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

У меня есть шрифт, который можно копировать, но копирование может быть дорогостоящим. Я реализовал конструктор перемещения и назначение перемещения. Но у меня проблемы с производительностью, когда люди забывают вызвать move () при передаче по значению.

Хорошо ли в стиле C ++ 11 удалить конструктор копирования и вместо этого предоставить явный метод copy () для тех редких случаев, когда действительно требуется копия? Это идиоматично для других языков (Ruby, JavaScript), но я не знаю ничего в стандартной библиотеке C ++, запрещающей копирование исключительно для повышения производительности. Например, std :: vector ‹> можно копировать, а std :: unique_ptr‹> и std :: thread нельзя копировать по другим причинам.


person Tim Culver    schedule 06.04.2013    source источник
comment
Я полагаю, что проблема с функцией-членом copy состоит в том, что она нарушает весь код шаблона, который использует оператор присваивания.   -  person Pubby    schedule 06.04.2013


Ответы (3)


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

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

T a;
T a = b;

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

T c = std::move(a); // I'm doing it right (if I no longer need object a);
T d = b; // If I don't need b anymore, I'm doing it wrong.

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

void foo(my_class&& obj);

my_class a;
foo(a); // ERROR!
foo(std::move(a)); // OK
person Andy Prowl    schedule 06.04.2013
comment
В некоторых контекстах копируемые типы не следует копировать с помощью конструкции копирования: virtual T* Clone() const = 0 для классов интерфейса (или неокончательных реализаций) выражает возможность копирования, но не может быть реализован как конструктор копирования. Теперь value_ptr<T> оболочки могут вернуть вас в мир конструирования копий, но сам класс T копируемый, но не имеет конструктора копирования (по крайней мере, не общедоступного). - person Yakk - Adam Nevraumont; 06.04.2013
comment
@Yakk: Я не согласен. Копирование любого стандартного контейнера может быть довольно дорогим, но его можно скопировать. Лично я считаю, что операция копирования имеет семантику, определяемую языком, и синтаксис, определяемый языком. Эти две вещи должны совпадать. - person Andy Prowl; 06.04.2013
comment
Думаю, вы хотели прокомментировать мой ответ, а не мой комментарий. :) - person Yakk - Adam Nevraumont; 06.04.2013
comment
@Yakk: Я прокомментировал первую версию вашего комментария :) Вы редактировали, пока я писал ответ, затем я отправил, и мой ответ не соответствует вашему комментарию так сильно, как раньше - person Andy Prowl; 06.04.2013
comment
В любом случае в стандартных контейнерах есть приоритет: хотя у прямого списка есть размер и size() может быть реализовано, это дорого. Таким образом, в стандарте не указано size(), и если вы хотите узнать размер, вы должны сами произвести подсчет. - person Yakk - Adam Nevraumont; 07.04.2013
comment
@Yakk: Это хороший момент. С другой стороны, стандартные контейнеры не имеют альтернативной функции-члена с именем compute_size() вместо стандартной size(), которая была бы параллелью Clone() или Duplicate() вместо простого использования конструктора копирования. Кроме того, мне кажется, что копирование - это нечто гораздо более фундаментальное, чем вычисление размера или поиск значения. - person Andy Prowl; 07.04.2013
comment
Я симпатизирую этой точке зрения, что конструктор копирования - это поддерживаемый синтаксис для семантически копируемого типа. Однако я считаю, что с C ++ 11 некопируемые типы стали более удобными. Например, вам больше не нужен конструктор копирования, чтобы просто поместить ваш тип в стандартный контейнер. - person Tim Culver; 07.04.2013
comment
@TimCulver: Конечно, у некопируемых типов есть причина для существования, и если что-то не требует копии, то это не должно выполняться (мне также нравится семантика перемещения, делающая контейнеры более гибкими). Я считаю, что если что-то требует семантики копирования, это должно быть обеспечено с помощью синтаксиса копирования. - person Andy Prowl; 07.04.2013

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

Возможность чего-либо копировать не означает, что это нужно реализовать в копируемом типе. Разработчик этого типа должен решить, следует ли его семантически копировать.

Я бы не назвал операцию, которая произвела дорогостоящую копию, «копией», а скорее «клонированием» или «дубликатом».

Например, вы могли бы сделать это:

#include <utility>

template<typename T>
struct DoCopy {
  T const& t;
  DoCopy( T const& t_ ):t(t_) {}
};
template<typename T>
DoCopy<T> do_copy( T const& t ) {
  return t;
}
struct Foo {
  struct ExpensiveToCopy {
    int _[100000000];
  };
  ExpensiveToCopy* data;
  Foo():data(new ExpensiveToCopy()) {}
  ~Foo(){ delete data; }
  Foo(Foo&& o):data(o.data) { o.data = nullptr; }
  Foo& operator=(Foo&& o) { data=o.data; o.data=nullptr; return *this; }
  Foo& operator=(DoCopy<Foo> o) {
    delete data;
    if (o.t.data) {
      data=new ExpensiveToCopy(*o.t.data);
    } else {
      data=new ExpensiveToCopy();
    }
    return *this;
  }
  Foo( DoCopy<Foo> cp ):data(cp.t.data?new ExpensiveToCopy( *cp.t.data ):new ExpensiveToCopy() ) {};
};
int main() {
  Foo one;
  // Foo two = one; // illegal
  Foo three = std::move(one); // legal
  Foo four;
  Foo five = do_copy(three);
  four = std::move(three);
  five = do_copy(four);
}

Это в некоторой степени похоже на то, как вы могли написать std::move подобную семантику до существования ссылок на rvalue, с теми же недостатками, что и у таких методов, а именно: сам язык не знает, какие махинации вы затеваете.

Его преимущество состоит в том, что синтаксис приведенного выше do_copy аналогичен синтаксису std::move и позволяет использовать традиционные выражения без необходимости создавать тривиальные экземпляры Foo, а затем создавать копию другой переменной и т. Д.

Если ситуации, когда мы хотим рассматривать его как копируемый, обычны (если этого следует избегать), я бы написал обертку для копирования вокруг класса, который знает о методе duplicate.

person Yakk - Adam Nevraumont    schedule 06.04.2013
comment
Я бы позволил пользователю моей библиотеки быть судьей. - person Lightness Races in Orbit; 07.04.2013
comment
Мне нравится это решение. Я сделал нечто подобное для копируемых иерархий, и синтаксис, подобный перемещению, стал приятным штрихом. - person Tim Culver; 09.04.2013

Нет. Если тип копируемый, значит тип копируемый. Это означает, что его конструктор копирования доступен и работает. Это не означает, что существует некоторая функция-член, имя которой выглядит как символы c, o, p и y, которая делает «что-то вроде почти аналогичного».

person Lightness Races in Orbit    schedule 06.04.2013
comment
Итак, если они отключают построение копии, то тип не копируется. Это дублируемый, клонируемый или какой-либо другой синонимичный глагол, но явно не копируемый. В чем проблема? - person Yakk - Adam Nevraumont; 06.04.2013
comment
@Yakk: Я согласен с вашей классификацией. Это проблема? Ну кто знает? Зависит от того, что вы хотите для своего типа. Но вы, конечно, не должны больше называть его копируемым, и что касается многих других частей кода (например, контейнеров), ваш тип просто никогда не будет копироваться, потому что вы так постановили. - person Lightness Races in Orbit; 07.04.2013
comment
Да, я не согласен с анализом. Я предлагаю сделать мой тип не копируемым, несмотря на то, что это явно значимый тип. - person Tim Culver; 07.04.2013