Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
class X
{
public:
X(const X&) = delete;
X& operator=(const X&) = delete;
X(X&& src) noexcept;
X& operator=(X&& src) noexcept;
// ...
};Это как раз правило хорошего стиля: "Если определяешь или =delete любую операцию по умолчанию, то определи или =delete их всех".
So as soon as any of the special functions is declared, the others should all be declared to avoid unwanted effects like turning all potential moves into more expensive copies, or making a class move-only.
Defining only the move operations or only the copy operations would have the same effect here, but stating the intent explicitly for each special member makes it more obvious to the reader.
Труд похвальный, но сам Страуструп уже над таким работает более масштабно: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#main
Не совсем вижу смысл в "конкуренции" без фактического предложения альтернативы. Может есть смысл заняться переводом?
x.DoIt(); // DoIt() &
X().DoIt(); // DoIt() &&1. На самом деле нельзя) Если вы хотите вызвать из конструктора/деструктора виртуальный метод, то вы хотите странного.Оконная библиотека. У вас есть абстрактный класс, который умеет группировать объекты и его многочисленные потомки — диалоговые окна, тулбары и прочее. Вам в конструктуре было бы неплохо узнать — а какой, собственно, будет размер «внутренностей» у вашего окна (тулбар и обычный диалог тут устроены сильно по разному, как вы понимаете). В Turbo Vision для Turbo Pascal — это сделано естественным образом, через виртуальные функции. В Tubro Vision для С++ — там костыль. Можно выбрать из несколькоих — но это всё равно будет костыль.
В с++ есть очень важная гарантия — методы класса не могут быть вызваны перед конструктором и после деструктора.
В с++ есть очень важная гарантия — методы класса не могут быть вызваны перед конструктором и после деструктора.Во-первых все ваши гарантии выеденного яйца не стоят, если в программе есть UB (это вам уже показали). Я если вы всегда пишете без ошибок — то нафига вам какие-либо гарантии.
Виртуальные методы в конструкторе/деструкторе родителя эту гарантию нарушает.А также их нарушает куча других конструкций. И? Почему мы вот один конкретный способ стерльбы по ногам закрыли (причём закрыли путём усложенения компилятора и рантайма, а не просто как-нибудь), и при этом кучу других методов — оставили?
И я, например, хочу эту гарантию гораздо больше, чем решить вашу конкретную проблему чуть более элегантным (как вам кажется) способом.Он не самый элегантный. Он самый естественный. И во всех языках, которые не прилагали специалиных усилий (C#/Java, Object Pascal, далее везде) он работает. А вот в C++ — его закрыли. Ради каких-то странных гарантий, которые всё равно можно нарушить!
Однако вы не согласны, что это некие вырожденные примеры?Да, конечно. Но вырожденными они являются только потому что виртуальные функции там вызывать всё равно нельзя.
А вот например ловить ошибку того, что я использую в своей функции переменную, у которой не сработал конструктор — гораздо больший гемор.Не больший гемор, чем когда вы используете default initialization и обнаруживаете, что у вас в переменной мусор. MSAN точно так же смог бы это отловить.
Кстати, вы похоже смотрели на другие языки. А как там решают данную проблему?Никак не решают. Потому что это — не проблема. «Конечный» конструктор (не вызванный из других) прописывает
vtable — и всё, объект «готов к труду и обороне». До того, как сработает первая строчка пользовательского кода. В Java для безопасности все остальные поля обнуляются.«Конечный» конструктор (не вызванный из других) прописывает vtable
А как конструктор узнает, что он конечный?
Скорее указатель на vtable прописывает вызывающий код
struct A{
A(){
// some important work
}
void do_work(){
// need internal initialized vars
}
};
struct Base{
Base(){
foo();
}
virtual void foo() { }
};
struct Derived : Base{
Derived() = default;
virtual void foo() override {
a.do_work();
}
private:
A a;
};
int main(){
Derived derived;
}
operator->, а разименовывать nullptr нельзя. Если operator-> то там другие правила, но он, в свою очередь, не может быть статическим, так что UB случится всё равно.Вам в конструктуре было бы неплохо узнать — а какой, собственно, будет размер «внутренностей» у вашего окна
1. Язык такую возможность позволяет — значит, можно. Пример: виртуальный метод tostring() и логирование, которое этот метод использует. Хочется использовать логирование и в конструкторе, и в деструкторе.Вот как раз логирование приводится как пример того, для чего всё это безумие нужно. Ибо так, как это реализовано в C++ — внутри конструктора A вы можете использовать логирование, но оно ничего не будет знать о том, что этому объекту суждено в будущем стать объектом B. И в деструкторе — тоже. вот так, типа безопасно.
2. Пара (ссылка на объект, код ошибки), возвращаемая фабрикой, будет более логичным решением.А что будет с объектом, которые не до конца сконструирован? Вызывать для него деструктор или нет? А если что-то удалось, что-то нет?
А что будет с объектом, которые не до конца сконструирован? Вызывать для него деструктор или нет? А если что-то удалось, что-то нет?
Предполагается, что объект либо конструируется полностью, либо не создаётся вообще. Очистка ресурсов в случае ошибки создания объекта — забота фабрики.
C++ — внутри конструктора A вы можете использовать логирование, но оно ничего не будет знать о том, что этому объекту суждено в будущем стать объектом B. И в деструкторе — тоже. вот так, типа безопасно.
Может быть всё-таки лучше как есть?Как есть — это как? Примерно так: берём простенькую программу, делаем небольшой рефакторинг… трах, бах, расчленёнка, кишки наружу.
В том же C# для переписывания синхронного кода в асинхронный требуется минимум телодвижений. Даже автоматические средства существуют.
close(2) вернулся с ошибкой — и да, это реально происходит и да, это-таки ошибка которую аккуратно написанные программы, например, emacs обрабатывают).Там же закрытие ресурса неявное, а оно у меня тут самое что ни на есть явное.Вопрос не в «явное/неявное». Вопрос в том, чтобы случайно не забыть закрыть и при этом обработать ошибки.
finally не нужен, потому что есть RAII — есть, а объяснений как решить с помощью этого RAII простейшую задачу — нет. Есть много громоздких и некрасивых решений, а хорошего — я не знаю.Если вы ожидаете ошибку при создании объекта, то это не исключительная ситуация.От языка зависит. В том же python итератор имеет один метод
next и кидает исключение, если всё закончилось.
Семантика копирования и управление ресурсами в C++