Эффективное / быстрое копирование для стандартных контейнеров, таких как std :: vector

Для индивидуального использования я унаследовал std::vector на заказ class Vector, Для моего требования это public наследство в порядке.

Одно из намерений состоит в том, чтобы избежать многократного создания копий векторного массива, поэтому я решил, что этот пользовательский класс основан на владении.

Vector<A> vA1(100); // vA1 is allocated A[100]
Vector<A> vA2 = vA1; // vA2 refers to original A[100] and ...
// ... vA1 is now blank which is expected

Вот как это реализовано для C ++ 03:

template<typename T>
struct Vector : std::vector<T>
{
// ... other constructors

Vector (const Vector &copy) // copy constructor
{
this->swap(const_cast<Vector&>(copy)); // <---- line of interest
}
// 'operator =' is TBD (either same as above or unimplemented)
};

Я нарушаю какое-либо языковое правило / особенность / соглашение? В этом коде есть что-то плохое?

редактировать: Я добавил свой новый подход в форме ответа ниже (вот его работа демонстрация).

0

Решение

Я думаю, что вы ищете семантику перемещения.

Vector<A> vA1(100); // vA1 is allocated A[100]
Vector<A> vA2 = std::move(vA1); // vA2 refers to original A[100] and ...
// ... vA1 is now blank which is expected

Если ваш компилятор не поддерживает семантику перемещения, я бы посоветовал вам использовать умные указатели или взглянуть на повышение :: движение который может эмулировать семантику перемещения в компиляторах до C ++ 11.

std::shared_ptr<Vector<A>> vA1(new Vector<A>(100)); // vA1 is allocated A[100]
std::shared_ptr<Vector<A>> vA2 = vA1; // vA2 refers to original A[100] and ...
vA1.reset();
// ... vA1 is now blank which is expected

или даже проще:

Vector<A> vA1(100); // vA1 is allocated A[100]
Vector<A> vA2;
vA1.swap(vA2);// vA2 refers to original A[100] and ...
// ... vA1 is now blank which is expected
4

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

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

В C ++ 11 подвижная семантика интегрирована в язык, и функции, включая конструкторы и присваивания, могут быть перегружены, чтобы вести себя по-разному в зависимости от того, является ли объект подвижным или нет.

3

Ваш код нарушает соглашения, которые поддерживают константность:

const Vector<int> foo(12);
const Vector<int> bar(foo); // UB.
some_function_that_takes_Vector_by_value(bar); // UB.

Вы можете сказать: «но никто никогда не создаст const экземпляр Vector, поэтому UB никогда не произойдет «. В этом случае вы могли бы по крайней мере заставить свой экземпляр ctor использовать неконстантную ссылку, чтобы следовать соглашению о том, что функции не должны изменять объекты, взятые const& параметры.

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

Проблема ненужных копий в C ++ 03 не нова, и IMO не требует нового решения. Многие программисты на C ++ уже знают, как воспользоваться swap и копируйте elision, и тем, кто не знает, как избежать ненужных копий, лучше научиться делать это обычным способом, чем научиться делать это уникальным способом, требуемым вашим конкретным классом.

2

С учетом комментариев / ответов, я понял, что мой нынешний подход не очень хорошая идея, чтобы иметь дело с вещами.
Я придумал измененный дизайн, в котором я пытаюсь смоделировать операцию «перемещение», используя swap() метод (считают их взаимозаменяемыми в этом контексте).

Для любого класса или контейнера, который хочет сделать более быстрое копирование (т.е. перемещать путем замены), следует наследовать ниже класса (непосредственно копировать вставку из реального файла):

  /*
* An empty CRTP base class for the classes which are allowing move sematics (effectively swap)
* A given class `X` must implement `void swap(X&);` method for the movement (i.e. content swapping)
* For example, `X` is deriving this class (i.e. `class X : public Movable<X> ...`)
* ... below is an illustration for how to transfer contents of an `X` object to another
* `X o1; ... X o2(o1.move()); // contents of o1 is now moved to o2`
*/
template<typename Derived>
class Movable
{
/*
* Empty constructor for normal behavior
*/
public: Movable ()
{}

/*
* Copy constructor which does nothing; without this compiler errors out !
*/
public: Movable (const Movable &copy)
{}

/*
* Move constructor which will effectively call `Derived::swap()` method
* After this, the contents of the object will be moved to each other
* For syntactic sugar, it has been made `explicit`
*/
public: explicit Movable (Movable &orig)
{
(*this)(orig);
}

/*
* Moving while Not initializing object, one need to use `operator ()` and not `operator =`
* If `operator =` is used then simply `move()` part will not happen; i.e. below 2 are same:
* `obj1 = obj2.move();` and `obj1 = obj2;`
*/
public: void operator () (Movable &orig)
{
static_cast<Derived*>(this)->swap(static_cast<Derived&>(orig));
}
/*
* This method is called from `Derived` class when move sematics is intended
*/
public: Movable& move ()
{
return *this;
}
};

Вот как это должно быть развернуто:

template<typename T>
struct Vector : std::vector<T>,
Movable<Vector<T> >  // inherit as CRTP base
{
// ... other methods
typedef Movable<Vector<T> > Movable_;
Vector (Movable_ &orig) : Movable_(orig) {}  // Declare a constructor
};

Вот как это должно быть использовано:

Vector<A> vA1(100); // vA1 is allocated A[100]
Vector<A> vA2 = vA1; // normal copying (no change)
vA2 = vA1; // normal assignment (no change)
Vector<A> vA3(vA1.move()); // <------- "moves" contents from 'vA1' to 'vA3'
vA1(vA2.move()); // <------- "moves" contents from 'vA2' to 'vA1'
vA2 = vA3.move(); // normal copying from 'vA3' to 'vA2' ('vA3' unaffected)
1