Есть ли варианты использования для класса, который можно копировать, но не перемещать?

После прочтения этот недавний вопрос @Mehrdad на какие классы должны быть сделаны неподвижный и, следовательно, не копируемый, Я начал задаваться вопросом, есть ли варианты использования для класса, который можно копировать, но не перемещать. Технически это возможно:

struct S
{
S() { }
S(S const& s) { }
S(S&&) = delete;
};

S foo()
{
S s1;
S s2(s1); // OK (copyable)
return s1; // ERROR! (non-movable)
}

Хотя S имеет конструктор копирования, он явно не моделирует CopyConstructible концепция, потому что это, в свою очередь, уточнение MoveConstructible Концепция, которая требует наличия (не удаленного) конструктора перемещения (см. § 17.6.3.1/2, Таблица 21).

Есть ли вариант использования для типа, как S выше, что копируемый, но не CopyConstructible и неподвижный? Если не, почему это не запрещено объявить конструктор копирования а также удаленный конструктор перемещения в том же классе?

14

Решение

Предположим, у вас есть класс, который не дешевле переместить, чем скопировать (возможно, он содержит std::array типа POD).

Функционально, вы «должны» сделать это MoveConstructible так, чтобы S x = std::move(y); ведет себя как S x = y;и именно поэтому CopyConstructible является суб-концепцией MoveConstructible. Обычно, если вы вообще не объявляете конструкторов, это «просто работает».

На практике, я полагаю, что вы можете временно отключите конструктор перемещения, чтобы определить, есть ли в вашей программе какой-либо код, появляется более эффективным, чем на самом деле, путем перемещения экземпляров S, Мне кажется чрезмерным запрещать это. Задача стандарта — не навязывать хороший дизайн интерфейса в законченном коде 🙂

11

Другие решения

В настоящее время я не знаю ни одного варианта использования для удаленного конструктора / назначения перемещения. Если это сделано небрежно, то это будет ненужно препятствовать возвращению типа из фабричной функции или помещению в std::vector,

Тем не менее, удаленные участники хода все же являются законными на тот случай, если кто-то может найти для них применение. В качестве аналогии я знал, что бесполезно const&& годами. Люди спрашивали меня, не должны ли мы просто объявить это вне закона. Но в конечном итоге несколько случаев использования появились после того, как мы получили достаточный опыт работы с этой функцией. Такой же может быть также происходит с удаленными участниками перемещения, но, насколько я знаю, пока нет.

8

Я не думаю, что может быть какой-то разумный класс, который бы помешал переехать, пока позволяют копия. Из той же темы ясно, что перемещение — это эффективный способ копирования, когда вам больше не нужен исходный объект.

2

Я смотрел на эту проблему сегодня, потому что мы портировали некоторый код из VS2005 в VS2010 и начали видеть повреждение памяти. Оказалось, что оптимизация (во избежание копирования при поиске на карте) не вылилась в C ++ 11 с семантикой перемещения.

class CDeepCopy
{
protected:
char* m_pStr;
size_t m_length;

void clone( size_t length, const char* pStr )
{
m_length = length;
m_pStr = new char [m_length+1];
for ( size_t i = 0; i < length; ++i )
{
m_pStr[i] = pStr[i];
}
m_pStr[length] = '\0';
}

public:
CDeepCopy() : m_pStr( nullptr ), m_length( 0 )
{
}

CDeepCopy( const std::string& str )
{
clone( str.length(), str.c_str() );
}

CDeepCopy( const CDeepCopy& rhs )
{
clone( rhs.m_length, rhs.m_pStr );
}

CDeepCopy& operator=( const CDeepCopy& rhs )
{
if (this == &rhs)
return *this;

clone( rhs.m_length, rhs.m_pStr );
return *this;
}

bool operator<( const CDeepCopy& rhs ) const
{
if (m_length < rhs.m_length)
return true;
else if (rhs.m_length < m_length)
return false;

return strcmp( m_pStr, rhs.m_pStr ) < 0;
}

virtual ~CDeepCopy()
{
delete [] m_pStr;
}
};

class CShallowCopy : public CDeepCopy
{
public:

CShallowCopy( const std::string& str ) : CDeepCopy()
{
m_pStr = const_cast<char*>(str.c_str());
m_length = str.length();
}

~CShallowCopy()
{
m_pStr = nullptr;
}
};

int _tmain(int argc, _TCHAR* argv[])
{
std::map<CDeepCopy, int> entries;
std::string hello( "Hello" );

CDeepCopy key( hello );
entries[key] = 1;

// Named variable - ok
CShallowCopy key2( hello );
entries[key2] = 2;

// Unnamed variable - Oops, calls CDeepCopy( CDeepCopy&& )
entries[ CShallowCopy( hello ) ] = 3;

return 0;
}

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

2

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

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

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

Отказ от ответственности: некоторые пуристы C ++ будут указывать, что STL (и, следовательно, стандарт) определяет, что такое операция перемещения, и она не соответствует тому, что я описал здесь, поэтому я не буду спорить с этим.

0
По вопросам рекламы [email protected]