У меня есть обертка для некоторого устаревшего кода.
class A{
L* impl_; // the legacy object has to be in the heap, could be also unique_ptr
A(A const&) = delete;
L* duplicate(){L* ret; legacy_duplicate(impl_, &L); return ret;}
... // proper resource management here
};
В этом устаревшем коде функция, которая «дублирует» объект, не является потокобезопасной (при вызове того же первого аргумента), поэтому он не отмечен const
в обертке. Я предполагаю следующие современные правила: https://herbsutter.com/2013/01/01/video-you-dont-know-const-and-mutable/
Этот duplicate
выглядит как хороший способ реализовать конструктор копирования, за исключением деталей, которые не являются const
. Поэтому я не могу сделать это напрямую:
class A{
L* impl_; // the legacy object has to be in the heap
A(A const& other) : L{other.duplicate()}{} // error calling a non-const function
L* duplicate(){L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
Так, как выход из этой парадоксальной ситуации?
(Скажем также, что legacy_duplicate
не нить- безопасно, но я знаю, оставляет объект в исходном состоянии, когда он выходит. Будучи C -функцией, поведение только документировано, но не имеет понятия постоянства.)
Я могу придумать много возможных scenar ios:
(1) Одна из возможностей состоит в том, что невозможно вообще реализовать конструктор копирования с обычной семантикой. (Да, я могу переместить объект, и это не то, что мне нужно.)
(2) С другой стороны, копирование объекта по своей сути не является потокобезопасным в том смысле, что копирование простого типа может найти источник в полу-модифицированном состоянии, поэтому я могу просто go переслать и сделать это, возможно,
class A{
L* impl_;
A(A const& other) : L{const_cast<A&>(other).duplicate()}{} // error calling a non-const function
L* duplicate(){L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
(3) или даже просто объявить duplicate
const и l ie о безопасности потоков во всех контекстах. (В конце концов, устаревшая функция не заботится о const
, поэтому компилятор даже не будет жаловаться.)
class A{
L* impl_;
A(A const& other) : L{other.duplicate()}{}
L* duplicate() const{L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
(4) Наконец, я могу следовать логике c и создайте конструктор копирования, который принимает неконстантный аргумент.
class A{
L* impl_;
A(A const&) = delete;
A(A& other) : L{other.duplicate()}{}
L* duplicate(){L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
Оказывается, это работает во многих контекстах, потому что эти объекты обычно не const
.
Вопрос в том, является ли это действительным или распространенным маршрутом?
Я не могу назвать их, но я интуитивно ожидаю, что в будущем возникнет множество проблем, связанных с Конструктор константного копирования. Вероятно, из-за этой тонкости он не будет квалифицироваться как тип значения.
(5) Наконец, хотя это кажется излишним и может иметь большие затраты времени выполнения, я мог бы добавить мьютекс:
class A{
L* impl_;
A(A const& other) : L{other.duplicate_locked()}{}
L* duplicate(){
L* ret; legacy_duplicate(impl_, &ret); return ret;
}
L* duplicate_locked() const{
std::lock_guard<std::mutex> lk(mut);
L* ret; legacy_duplicate(impl_, &ret); return ret;
}
mutable std::mutex mut;
};
Но принуждение к этому похоже на пессимизацию и делает класс больше. Я не уверена. В настоящее время я склоняюсь к (4) или (5) или их комбинации.
РЕДАКТИРОВАТЬ 1:
Другой вариант:
(6) Забудьте обо всех бессмысленных дублирующую функцию-член и просто вызовите legacy_duplicate
из конструктора и объявите, что конструктор копирования не является потокобезопасным. (И, если необходимо, сделайте еще один потокобезопасный вариант типа, A_mt
)
class A{
L* impl_;
A(A const& other){legacy_duplicate(other.impl_, &impl_);}
};
РЕДАКТИРОВАТЬ 2:
Это может быть хорошим модель для того, что делает унаследованная функция. Обратите внимание, что касаясь ввода, вызов не является потокобезопасным относительно значения, представленного первым аргументом.
void legacy_duplicate(L* in, L** out){
*out = new L{};
char tmp = in[0];
in[0] = tmp;
std::memcpy(*out, in, sizeof *in); return;
}
РЕДАКТИРОВАТЬ 3: Недавно я узнал, что std::auto_ptr
была похожая проблема с неконстантным конструктором копирования. В результате auto_ptr
нельзя было использовать внутри контейнера. https://www.quantstart.com/articles/STL-Containers-and-Auto_ptrs-Why-They-Dont-Mix/