Подтвердить что ты не робот

Существуют ли какие-либо варианты использования для класса, который можно копировать, но не перемещать?

После чтения этого недавнего вопроса от @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 и не движется? Если нет, почему не запрещено объявлять конструктор копирования и конструктор удаленных перемещений в том же классе?

4b9b3361

Ответ 1

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

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

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

Ответ 2

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

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

Ответ 3

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

Ответ 4

Я рассматривал эту проблему сегодня, потому что мы портировали некоторый код из VS2005 в VS2010 и начали видеть повреждение памяти. Это оказалось потому, что оптимизация (чтобы избежать копирования при выполнении поиска по карте) не переводилась в С++ 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 использовался для первоначального поиска, тогда он будет скопирован, если это была вставка. Проблема в том, что этот подход не работает с семантикой перемещения.

Ответ 5

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

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

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

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