Страницы

Поиск по вопросам

Показаны сообщения с ярлыком конструктор-копирования. Показать все сообщения
Показаны сообщения с ярлыком конструктор-копирования. Показать все сообщения

среда, 22 января 2020 г.

“explicit” для конструкторов копирования и перемещения

#cpp #cpp11 #конструктор #конструктор_копирования


Надо ли (и если да, то в каких случаях) указывать конструкторы копирования и перемещения
как explicit? Видел такое в коде и не совсем понял. Где ненужное неявное преобразование,
которое explicit помогает нам избежать?
    


Ответы

Ответ 1



Ответ на этот вопрос прост: explicit нужен там, где он нужен, а где не нужен — там не нужен. Есть распространённый совет, что все конструкторы, что принимают один аргумент должны быть помечены explicit, во избежание случайного создания объектов класса там, где их создание не очевидно. К примеру, есть у нас функция void someFun(const std::shared_ptr&) и мы, совершенно случайно, используем её так: int* array = new int[55]; someFun(array); delete[] array; Если бы конструктор std::shared_ptr не был explicit, то код бы успешно скомпилировался и на третьей строчке мы бы получили неопределённое поведение. Но т.к. конструктор std::shared_ptr помечен как explicit мы не получим такую ситуацию случайно. Таким образом, explicit призван исключить случайные ошибки. Что касается explicit конструктора копирования: ни разу не видел такого и не могу придумать, зачем такой может быть нужен. Если мы хотим запретить копирование, то нужно удалить его вообще, если не хотим, то зачем вставлять палки в колёса разрешая лишь узкий набор синтаксических вариантов, используя которые можно будет скопировать объект? В качестве примера можно привести следующий код: #include #include using namespace std; class MeClass { public: MeClass(size_t idx, const string& tag = "") : m_Idx{idx}, m_Tag{tag} { } MeClass(const MeClass& rhs): m_Idx{rhs.m_Idx}, m_Tag{rhs.m_Tag} { } private: size_t m_Idx; string m_Tag; }; MeClass explicitReturn() { // Явный вызов конструктора // Неявный вызов конструктора копирования // move нужен исключительно для исключения C++17 RVO return move(MeClass{2, "meTag"}); } MeClass implicitReturn() { // Неявный вызов конструктора // Неявный вызов конструктора копирования (до C++17) return {3, "meaTag"}; } void funForFun(const MeClass& me) { } int main() { // Явный вызов конструктора MeClass me1{1}; // Неявный вызов конструктора MeClass me2 = 2; // Неявный вызов конструктора копирования auto me3 = me1; // Явный вызов конструктора копирования auto me4{me1}; // Неявный вызов конструктора funForFun(2); funForFun({3}); // Явный вызов конструктора funForFun(me1); }; Если пометить в коде конструктор как explicit, то перестанут работать примеры, где указано явное его использование. То же самое с конструктором копирования.

Ответ 2



Ключевое слово explict позволит избежать случайного преобразования типов, которое может привести к неявным ошибкам. Например: У вас есть класс с конструктором MyString(int size), который создает строку размером size. Также есть метод print(const MyString&), при вызове print(3) ты можешь ожидать вызов print("3"), а на самом деле получишь пустую строку из 3 символов. Хороший и развернутый ответ можно найти тут.

Ответ 3



Если у Вас будут в дальнейшем определены или явно удалены операторы копирования и перемещения, то в этом нет никакой обходимости.

вторник, 11 июня 2019 г.

Конструктор копии для классов

Пытаюсь присвоить значения вектора v1 k v2 но компилятор дает ошибку. Где ошибка? И ещё один вопрос, как перезагружать оператор присваивания ???
Process returned -1073741819 (0xC0000005) execution time : 1.253 s Press any key to continue.
Вот код
#include using namespace std; class vector_c { private: int* vector1; int Max; int index; public: vector_c(int sizze) { index=0; Max=sizze; vector1 = new int[Max]; };
vector_c(vector_c &c)//copy construck {
index=c.index; Max = c.Max; vector1 = new int[Max]; for (int i = 1; i <=Max; i++) { vector1[i] = c.vector1[i];
} }; ~vector_c() { delete [] vector1; }; void max_znachenie() { int index1=0; index1 = vector1[1]; for(int i=1; i<=Max;i++) { if(vector1[i] > index1 ) index1 = vector1[i]; } cout << index1 << endl; };
void get_inf(int element) { vector1[++index]=element; };
int out_inf() { return vector1[index--]; };
};

int main() { vector_c v1(3); v1.get_inf(78); v1.get_inf(2); v1.get_inf(5); v1.max_znachenie(); cout << endl; cout << endl << v1.out_inf() << endl << v1.out_inf() << endl << v1.out_inf() << endl; vector_c v2=v1; cout << v2.out_inf(); return 0; }


Ответ

Прогнала ваш код в дебаггере. Есть 2 проблемы. В конструкторе копирования копируются не все поля. Надо добавить.
index=c.index;
Функция переписана.
void get_inf(int element) { vector1[index++]=element; };
Пример перегрузки оператора присваивания
class vector_c { private: int* vector1; int Max; int index; public: vector_c(int sizze) { index=0; Max=sizze; vector1 = new int[Max]; };
vector_c& operator=(const vector_c& right) { //проверка на самоприсваивание if (this == &right) { return *this; } // Здесь скопируйте все значения return *this; } };

Пользовательский конструктор копирования/перемещения

Правильно ли я понимаю, что будет плохой техникой реализовывать конструкторы копирования/перемещения, используя соответствующие операторы присваивания?
Т.е. лучше ли использовать списки инициализации, соответственно, в случае перемещения в них надо указывать дополнительно при надобности приведение к rvalue с помощью std::move(), а в теле перемещающего конструктора «обнулить» rhs (это по соглашению проекта)? Ну и какие-то дополнительные операции в теле, например, копирование поэлементное массива или логирование.


Ответ

Вообще говоря, да, это будет странной техникой.
С общей точки зрения попытки реализовать конструктор копирования через копирующий оператор присваивания будут страдать от проблемы "двойной инициализации": так как оператор присваивания ожидает на вход уже сконструированные объекты, в конструкторе придется сначала конструировать какой-то (пустой) объект, а лишь затем вызывать для него оператор присваивания, который будет "переконструировать" этот объект по-новому.
В устоявшихся идиомах обычно делают наоборот: копирующий оператор присваивания реализуется через конструктор копирования. Так это делается, например, в известной идиоме Copy-And-Swap.

вторник, 12 марта 2019 г.

“explicit” для конструкторов копирования и перемещения

Надо ли (и если да, то в каких случаях) указывать конструкторы копирования и перемещения как explicit? Видел такое в коде и не совсем понял. Где ненужное неявное преобразование, которое explicit помогает нам избежать?


Ответ

Ответ на этот вопрос прост: explicit нужен там, где он нужен, а где не нужен — там не нужен. Есть распространённый совет, что все конструкторы, что принимают один аргумент должны быть помечены explicit, во избежание случайного создания объектов класса там, где их создание не очевидно. К примеру, есть у нас функция void someFun(const std::shared_ptr&) и мы, совершенно случайно, используем её так:
int* array = new int[55]; someFun(array); delete[] array;
Если бы конструктор std::shared_ptr не был explicit, то код бы успешно скомпилировался и на третьей строчке мы бы получили неопределённое поведение. Но т.к. конструктор std::shared_ptr помечен как explicit мы не получим такую ситуацию случайно. Таким образом, explicit призван исключить случайные ошибки.
Что касается explicit конструктора копирования: ни разу не видел такого и не могу придумать, зачем такой может быть нужен. Если мы хотим запретить копирование, то нужно удалить его вообще, если не хотим, то зачем вставлять палки в колёса разрешая лишь узкий набор синтаксических вариантов, используя которые можно будет скопировать объект?
В качестве примера можно привести следующий код:
#include #include
using namespace std;
class MeClass { public: MeClass(size_t idx, const string& tag = "") : m_Idx{idx}, m_Tag{tag} { } MeClass(const MeClass& rhs): m_Idx{rhs.m_Idx}, m_Tag{rhs.m_Tag} { } private: size_t m_Idx; string m_Tag; };
MeClass explicitReturn() { // Явный вызов конструктора // Неявный вызов конструктора копирования // move нужен исключительно для исключения C++17 RVO return move(MeClass{2, "meTag"}); }
MeClass implicitReturn() { // Неявный вызов конструктора // Неявный вызов конструктора копирования (до C++17) return {3, "meaTag"}; }
void funForFun(const MeClass& me) {
}
int main() { // Явный вызов конструктора MeClass me1{1}; // Неявный вызов конструктора MeClass me2 = 2; // Неявный вызов конструктора копирования auto me3 = me1; // Явный вызов конструктора копирования auto me4{me1}; // Неявный вызов конструктора funForFun(2); funForFun({3}); // Явный вызов конструктора funForFun(me1); };
Если пометить в коде конструктор как explicit, то перестанут работать примеры, где указано явное его использование. То же самое с конструктором копирования.