Pull to refresh

Comments 21

Корректный код: printf("%s", name().c_str());

Разве puts уже отменили?

Нет, конечно. Это на случай, если непременно хочется использовать printf.

С чрезмерным использованием классов я в первый раз столкнулся в проекте на Java: куча абстрактных и "пустых" (без полей и методов - просто для наследования) классов, в добавок, к интерфейсам.

Классы, на мой взгляд, помогают структуризовать код и улучшить понимание, но всего должно быть в меру.

Добавлю, что, с моей точки зрения, классы стоит использовать лишь в том случае, когда предполагается (хотя бы в будущем) использовать наследование и полиморфизм; если заведомо ясно, что ни того, ни другого не требуется и не потребуется, то и смысла в классе нет (инкапсуляцию можно обеспечить другими методами).

инкапсуляцию можно обеспечить другими методами

Например?

В заголовочном файле разместить только интерфейсную часть, а всю реализацию, включая внутренние структуры данных, -- в .cpp. Правда, этот способ может быть неудобным/костыльным, если нужно создать несколько однотипных объектов (по сути, наборов данных), не давая при этом доступа к их внутренней реализации. В такой ситуации более правильным, скорей всего, будет использование "недокласса": class/struct с разделением на public/private, но без виртуальных функций и без наследования.

Что значит "интерфейсная часть"? Вы говорите про opaque types в стиле чистого Си?

struct my_data;

my_data * my_data_create();
void my_data_destroy(my_data * obj);
int my_data_get_x(my_data * obj);
void my_data_set_x(my_data * obj, int x);

?

Примерно так, да -- "голые" функции (и публично доступные структуры, необходимые для взаимодействия с ними -- для параметров, например, или, как в Вашем примере, только их объявления, но не определения, для сокрытия их содержимого), а не методы (функции) классов. Разве что, при необходимости всё это можно "завернуть" в отдельное пространство имён. Можно, конечно, все их поместить внутрь недокласса -- но особого смысла не вижу.

Тогда поздравляю, ваши "другие методы" для обеспечения инкапсуляции напрочь убивают возможность размещения объектов на стеке или агрегации в другие объекты.

Не, конечно, можно заморочиться на что-то вроде:

// Заголовочный файл.
struct my_data {
  alignas(int) char buffer_[32];
};

void my_data_init(my_data &);
void my_data_destroy(my_data &);
int my_data_get_x(my_data &);
...

// Файл реализации.
struct actual_my_data {
  int x_;
  ...
}; // Как-то гарантируем, что sizeof(actual_my_data) == sizeof(my_data::buffer_).
  // И надеемся, что с выравниванием my_data::buffer_ не налажали.

void my_data_init(my_data & d) {
  new(d.buffer_) actual_my_data;
}
void my_data_destroy(my_data & d) {
  auto * p = reinterpret_cast<actual_my_data>(d.buffer_);
  p->~actual_my_data();
}
int my_data_get_x(my_data & d) {
  auto * p = reinterpret_cast<actual_my_data>(d.buffer_);
  return p->x_;
}

Но, блин, нафига зачем?

Я ж и говорю: это может быть неудобно/костыльно. Надо из задачи исходить. Скажем, если нужно реализовать несколько связанных между собой достаточно низкоуровневых функций -- типа классического ввода-вывода в стиле C, -- то нет никакой нужды объединять их в класс, достаточно непрозрачной структуры а-ля FILE и хранения указателя на неё для передачи в качестве параметра. Если нужны несколько функций, связанных только назначением, но не параметрами, тогда и структуры такой не требуется -- просто несколько объявлений лежат вместе в одном заголовке, и всё.

то нет никакой нужды объединять их в класс, достаточно непрозрачной структуры а-ля FILE и хранения указателя на неё для передачи в качестве параметра

Еще раз: вы не сможете нормально размещать экземпляры таких непрозрачных структур на стеке или внутри других объектов. Попробуйте таким образом представить, например, std::string_view.

Т.е., отказываясь от имеющейся в C++ инкапсуляции (т.е. содержимое класса видно всем, но доступ регламентируется посредством public/protected/private) вы теряете в эффективности. А потеря эффективности может обеспечить вам такую "нужду", что мало не покажется.

Если нужны несколько функций, связанных только назначением, но не параметрами, тогда и структуры такой не требуется -- просто несколько объявлений лежат вместе в одном заголовке, и всё.

Инкапсуляция к этому отношения не имеет вообще.

"Еще раз: вы не сможете нормально размещать экземпляры таких непрозрачных структур на стеке или внутри других объектов."

(не пойму, как здесь делать цитаты...)

1) Это не всегда нужно -- например, правильней может быть хранение именно указателя на объект, а не самого объекта.

2) С эффективностью это не очень-то связано. Если Вы передаёте в функцию сам объект, Вы как раз теряете в эффективности, а если всегда передаётся только указатель, то зачем хранить объект в стеке? Особенно объект потенциально долгоиграющий, который должен сохраняться при выходе из функции, где он создаётся, а соответственно, создаваемый в динамически выделяемой памяти, а не в стеке? (То же открытие классического файла).

"Инкапсуляция к этому отношения не имеет вообще."

Имеет. То, что функции не связаны между собой параметрами, не означает, что между ними нет никакой внутренней связи. Скажем, несколько функций, выводящих разную информацию через один и тот же отладочный интерфейс.

Это не всегда нужно -- например, правильней может быть хранение именно указателя на объект, а не самого объекта.

Если вы используете обычные объекты C++, то вы можете хранить как указатель, так и весь объект. Тогда как с old plain C's opaque types вы ограничены только возможностью хранения указателя.

С эффективностью это не очень-то связано.

Еще как связано. В рамках обычного C++ я могу написать:

class many_references {
  std::string_view a_;
  std::string_view b_;
  std::string_view c_;
  std::string_view d_;
  ...
};

И все эти ссылки будут лежать компактно в памяти рядышком друг с другом. Да еще и компилятор может инлайнить тривиальные вызовы методов-геттеров.

Если вы попробуете заменить std::string_view на opaque type, да еще и с динамической аллокацией, то вы потеряете и по расходу памяти, и по временам доступа к содержимому.

Простите, комментировать про "на стеке" или "если он долгоиграющий" не буду, мат (да и вообще комментарии в стиле правды-матки) здесь не любят. Но у вас каша в голове.

То, что функции не связаны между собой параметрами, не означает, что между ними нет никакой внутренней связи.

Если у вас есть две функции, которые не связаны общим параметром, но они таки работают над общими данными, значит эти данные где-то на уровне глобальных переменных (пусть даже и спрятанных от посторонних глаз). А глобальные переменные к инкапсуляции имеют очень и очень далекое отношение.

Совет №56 весьма спорный в отношении C++.

В отличие от питона тут могут быть полезные классы чуть ли не вообще без методов. Например я предпочту использовать std::chrono::duration вместо int для хранения интервала времени.

И так во многом. Конечно не надо впадать в крайности. Но это справедливо в обоих отношениях, как не надо делать "лишних" классов (когда они реально лишние), так-же не надо и стремиться к тому чтобы пытаться обходиться без классов, там где они все-же будут полезны.

Совет №57 тоже не бесспорный.

С одной стороны в сообществе есть консенсус, что учить C++ лучше по хорошим книгам. С другой стороны, люди все-же разные. Есть такие, кто усваивает информацию лучше из книг, есть кто лучше из аудио, есть кто лучше из видео, а есть такие, кому проще самому во всем разобратся начав писать код.

Ну т.е. как совет для большинства вроде и ок, но как безусловно "вредыный совет" для всех - не ок.

Совет №56 весьма спорный в отношении C++. В отличие от питона тут могут быть полезные классы чуть ли не вообще без методов.

Так вроде и не сказано обратного - что совсем не надо в классы. Как раз таки, имеется ввиду что нужен баланс, ну т.е. не упарываться и если старая добрая сишная функция как нельзя лучше подходит - не надо в угоду идеологии оборачивать все это дело в класс, ну и наоборот.

Количество нулевых битов это ж старый "прикол над салагами", когда компилятор заменяет тщательно оптимизированную функцию одним вызовом POPCNT.

Количество нулевых битов это ж старый "прикол над салагами", когда компилятор заменяет тщательно оптимизированную функцию одним вызовом POPCNT.

Но не старше, чем Barcelona/10h/K10 (AMD, 2007) и Nehalem (Intel, 2008), в которых была реализована инструкция POPCNT. Разумеется, это если мы говорим не про мейнфреймы, в которых аналогичная инструкция существует действительно очень давно.

Задание вполне можно переформулировать таким образом, чтобы соискатель продемонстрировал свои знания в различных средах и микроархитектурах.

Тогда, возможно, получится увидеть применение __builtin_popcount и std::popcount, а не только классический алгоритм hamming weight.

Ещё мы просим написать на бумаге пару небольших функций, например "посчитать количество нулевых бит в переменной типа unsigned int". Это сразу очень много говорит о кандидате.

Вот если я на собеседовании (или в мире, где интернет вдруг исчез), то моим решением будет в цикле извлекать каждый бит через побитовое И по маске, сравнивать с нулём и ++n. Но также я помню, что «был какой-то там однострочник, трюк со степенью двойки, правда, не помню, какой» — и без гугления я его может и придумаю/вспомню, но на это уйдёт реально много времени, где-то день. Что это, на ваш взгляд, обо мне скажет? (Мне правда интересно.)

Написал обыкновенный вариант - молодец.

Дополнительно сказал, что был какой-то прём/инструкция - молодец++.

с этой серией статей я понял, что жизнь слишком коротка, чтобы кодить на C++.

Sign up to leave a comment.