ОБНОВЛЕНИЕ внизу
q1: Как бы вы реализовали правило пяти для класса, который управляет довольно большими ресурсами,но из чего вы хотите, чтобы он передавался по значению, потому что это значительно упрощает и украшает его использование?Или же не все пять элементов правила даже необходимы?
На практике я начинаю что-то с 3D-изображения, где изображение обычно 128 * 128 * 128, удваивается.Хотя возможность писать такие вещи значительно упростит математику:
Data a = MakeData();
Data c = 5 * a + ( 1 + MakeMoreData() ) / 3;
q2: Используя комбинацию семантики копирования elision / RVO / move, компилятор сможет это сделатьэто с минимумом копирования, нет?
Я попытался выяснить, как это сделать, поэтому я начал с основ;предположим, что объект реализует традиционный способ реализации копирования и присваивания:
class AnObject
{
public:
AnObject( size_t n = 0 ) :
n( n ),
a( new int[ n ] )
{}
AnObject( const AnObject& rh ) :
n( rh.n ),
a( new int[ rh.n ] )
{
std::copy( rh.a, rh.a + n, a );
}
AnObject& operator = ( AnObject rh )
{
swap( *this, rh );
return *this;
}
friend void swap( AnObject& first, AnObject& second )
{
std::swap( first.n, second.n );
std::swap( first.a, second.a );
}
~AnObject()
{
delete [] a;
}
private:
size_t n;
int* a;
};
Теперь введите rvalues и переместите семантику.Насколько я могу судить, это была бы рабочая реализация:
AnObject( AnObject&& rh ) :
n( rh.n ),
a( rh.a )
{
rh.n = 0;
rh.a = nullptr;
}
AnObject& operator = ( AnObject&& rh )
{
n = rh.n;
a = rh.a;
rh.n = 0;
rh.a = nullptr;
return *this;
}
Однако компилятор (VC ++ 2010 SP1) не слишком доволен этим, и компиляторы обычно верны:
AnObject make()
{
return AnObject();
}
int main()
{
AnObject a;
a = make(); //error C2593: 'operator =' is ambiguous
}
q3: Как это решить?Возвращаясь к AnObject & operator = (const AnObject & rh), конечно, это исправляет, но разве мы не теряем довольно важную возможность оптимизации?
Кроме того, ясно, что код для конструктора перемещения и присваивания полондублирования.Итак, пока мы забываем о неоднозначности и пытаемся решить эту проблему, используя copy и swap, но теперь для rvalues.Как объяснено здесь , нам даже не понадобится пользовательский своп, а вместо этого будет работать std :: swap, что звучит очень многообещающе.Поэтому я написал следующее, надеясь, что std :: swap скопирует конструкцию временного объекта с использованием конструктора перемещения, а затем поменяет его на * this:
AnObject& operator = ( AnObject&& rh )
{
std::swap( *this, rh );
return *this;
}
Но это не сработает и вместо этого приведет к стекупереполнение из-за бесконечной рекурсии, поскольку std :: swap снова вызывает наш оператор = (AnObject && rh). q4: Может ли кто-нибудь привести пример того, что подразумевается в этом примере?
Мы можем решить эту проблему, предоставив вторую функцию свопинга:
AnObject( AnObject&& rh )
{
swap( *this, std::move( rh ) );
}
AnObject& operator = ( AnObject&& rh )
{
swap( *this, std::move( rh ) );
return *this;
}
friend void swap( AnObject& first, AnObject&& second )
{
first.n = second.n;
first.a = second.a;
second.n = 0;
second.a = nullptr;
}
Теперь естьпочти вдвое больше кода суммы, однако часть движения окупается за счет довольно дешевого перемещения;но, с другой стороны, обычное назначение больше не может быть использовано при копировании.На данный момент я действительно запутался, и больше не вижу, что правильно и что неправильно, поэтому я надеюсь получить некоторую информацию здесь ..
ОБНОВЛЕНИЕ Так что, кажется, есть дваcamps:
- один говорит пропустить оператор присваивания перемещения и продолжать делать то, чему нас научил C ++ 03, то есть написать единственный оператор присваивания, который передает аргумент по значению.
- другой говорит, что нужно реализовать оператор присваивания перемещения (в конце концов, теперь это C ++ 11) и заставить оператор присваивания копии принимать его аргумент по ссылке.
(хорошо, и третий лагерь говорит мнеиспользовать вектор, но это как бы выходит за рамки этого гипотетического класса. Хорошо, в реальной жизни я бы использовал вектор, и были бы также другие члены, но так как конструктор / назначение перемещения не генерируются автоматически (пока?)вопрос все еще остается в силе)
К сожалению, я не могу протестировать обе реализации в реальном сценарии, так как этот проект только начался иКак на самом деле будут передаваться данные, пока неизвестно.Поэтому я просто реализовал их оба, добавил счетчики для распределения и т. Д. И выполнил пару итераций ок.этот код, где T - одна из реализаций:
template< class T >
T make() { return T( narraySize ); }
template< class T >
void assign( T& r ) { r = make< T >(); }
template< class T >
void Test()
{
T a;
T b;
for( size_t i = 0 ; i < numIter ; ++i )
{
assign( a );
assign( b );
T d( a );
T e( b );
T f( make< T >() );
T g( make< T >() + make< T >() );
}
}
Либо этот код недостаточно хорош для проверки того, что мне нужно, либо компилятор слишком умен: не важно, что я используюдля arraySize и numIter результаты для обоих лагерей в значительной степени идентичны: одинаковое количество распределений, очень незначительные изменения во времени, но без воспроизводимой существенной разницы.
Так что, если кто-то не может указать на лучший способ проверить это (учитывая, что фактическое использование scnearios еще не известно), я должен буду заключить, что это не имеет значения и, следовательно, оставлено на вкус разработчика.,В этом случае я бы выбрал # 2.