, если сильно помогает производительности, если вы, например, поместите свой класс в

ИТЬ внизу

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;
};

Теперь введите значения и переместите семантику. Насколько я могу судить, это будет рабочая реализация:

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;
}

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

ОБНОВИТЬ Так что, похоже, есть два лагеря:

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

(Хорошо, и есть 3-й лагерь, в котором говорится, что я должен использовать вектор, но это как-то выходит за рамки этого гипотетического класса. Хорошо, в реальной жизни я бы использовал вектор, и были бы и другие члены, но так как конструктор перемещения / задание не генерируется автоматически (пока?) вопрос все равно останется)

К сожалению, я не могу протестировать обе реализации в реальном сценарии, так как этот проект только начался, и как на самом деле будут передаваться данные, пока неизвестно. Поэтому я просто реализовал их оба, добавил счетчики для распределения и т. Д. И выполнил пару итераций ок. этот код, где 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.

Ответы на вопрос(6)

Ваш ответ на вопрос