возврат вектора по значению с множественной вложенностью функций в C++

По какой-то причине я хочу вернуть объект my::Vector (который в основном является классом-оболочкой, который внутренне использует вектор STL для фактического хранения, а также предоставляет некоторые дополнительные функции). Я возвращаю вектор по значению, поскольку функция каждый раз создает вектор локально.

my::Vector<int> calcOnCPU()
{
  my::Vector<int> v....
  return v;
} 

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

my::Vector<int> calc()
{
    if(...)
      return calcOnCPU();
}

Насколько мне известно, возврат по значению вызовет конструктор копирования класса my::Vector, что-то вроде:

Vector<int>::Vector(const Vector& c)
{
   ....
   m_vec = c.m_vec; // where m_vec is std::vector<int>
}

Несколько вопросов: 1) В конструкторе копирования вызывается ли конструктор копирования std::vector? или оператор присваивания и Просто для подтверждения, std::vector создает глубокую копию (то есть копирует все элементы с учетом базового целочисленного типа). 2) При вложении calcOnCPU() в calc() каждый возвращаемый вектор из int: будет создаваться 2 или 1 копия вектора? Как я мог избежать нескольких копий в случае такой простой вложенности методов? Встроенные функции или есть другой способ?

ОБНОВЛЕНИЕ 1: мне стало очевидно, что мне нужно сохранить свой собственный конструктор копирования, так как есть некоторые пользовательские требования. Однако я сделал простой тест в основной функции:

int main() {
   ...
   my::Vector v = calc();
   std::cout<<v;
}

Я поместил несколько отпечатков, используя «std::cerr» в своем конструкторе копирования, чтобы увидеть, когда он будет вызван. Интересно, что она не вызывается ни разу для вышеуказанной программы (по крайней мере, ничего не печатается). Это оптимизация копирования? Я использую компилятор GNU C++ (g++) v4.6.3 в Linux.


person usman    schedule 06.07.2012    source источник
comment
В готовящемся (текущем?) стандарте С++ есть семантика перемещения. И да, векторы STL делают глубокое копирование, и это то, что вы получаете с вашим кодом.   -  person fork0    schedule 06.07.2012
comment
ссылки Rvalue и конструкторы перемещения   -  person fork0    schedule 06.07.2012
comment
Если вы пытаетесь избавиться от временных копий, возможно, вы могли бы сделать так, чтобы каждая вложенная функция принимала ссылочный параметр для вашего вектора, который вы изменяете...   -  person Mihai Todor    schedule 06.07.2012
comment
Нам нужно увидеть определение вашего класса my::Vector, чтобы точно знать ответы на ваши вопросы. И вы уверены, что вам нужен класс-оболочка для добавления дополнительных функций? Разве это не может быть обработано бесплатными функциями?   -  person Benjamin Lindley    schedule 06.07.2012
comment
@MihaiTodor Спасибо, но я хочу иметь точно такой же интерфейс для всех функций в рамках дизайна библиотеки.   -  person usman    schedule 06.07.2012
comment
@BenjaminLindley Спасибо. Да, я думаю, мне нужен класс-оболочка, поскольку я предлагаю пользователю интерфейс std::vector, но внутренне выполняю управление памятью на устройствах CUDA и OpenCL векторных данных.   -  person usman    schedule 06.07.2012
comment
Я пробовал печатать std::cerr в конструкторе копирования, чтобы узнать, сколько раз он вызывается, но кажется, что он вообще не вызывается (ни разу)? Происходящее, как я уверен, должно называться.   -  person usman    schedule 06.07.2012
comment
Копировать исключение   -  person Benjamin Lindley    schedule 06.07.2012


Ответы (1)


В конструкторе копирования вызывается ли конструктор копирования std::vector? или оператор присваивания

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

Vector<int>::Vector(const Vector& c) : m_vec(c.m_vec) {
    ....
}

Просто чтобы подтвердить, std::vector создает глубокую копию

Да, копирование std::vector выделит новый блок памяти и скопирует в него все элементы.

При вложении calcOnCPU() в calc() каждый возвращаемый вектор int: будет создано 2 или 1 копия вектора?

Это зависит от компилятора. Он должен применять "оптимизацию возвращаемого значения" (частный случай удаления копии), и в этом случае он не будет создавать локальный объект и возвращать копию, но будет создать его непосредственно в пространстве, выделенном для возвращаемого объекта. В некоторых случаях это невозможно сделать, например, если у вас есть несколько операторов return, которые могут возвращать один из нескольких локальных объектов.

Кроме того, современный компилятор также будет поддерживать семантику перемещения, где, даже если копию нельзя исключить, содержимое вектора будет перемещено в возвращаемый объект, а не скопировано; то есть они будут переданы быстро путем установки внутренних указателей вектора без выделения памяти или копирования элементов. Однако, поскольку вы оборачиваете вектор в свой собственный класс и объявляете конструктор копирования, вам придется предоставить этому классу конструктор перемещения, чтобы он работал, что вы можете сделать, только если вы с помощью С++11.

Как я мог избежать нескольких копий в случае такой простой вложенности методов? Встроенные функции или есть другой способ?

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

person Mike Seymour    schedule 06.07.2012
comment
Спасибо за Ваш ответ. Одно разъяснение или удаление конструктора копирования, чтобы его можно было сгенерировать неявно. Что произойдет, если я удалю свой конструктор копирования? Какой неявно сгенерированный конструктор копирования может делать по-другому? - person usman; 06.07.2012
comment
@ user600029: Это не то, что может сделать неявно определенный конструктор копирования, а то, что вы запрещаете делать компилятору, если вы объявляете свой собственный. В частности, компилятор (C++11) предоставит конструктор перемещения, если это возможно, но не сделает этого, если вы предоставите конструктор копирования (или оператор присваивания...). То есть, не предоставив одну из операций, которые препятствуют созданию конструктора перемещения, вы можете получить прирост производительности бесплатно (или при обновлении компилятора, если вы все еще используете C++03). - person David Rodríguez - dribeas; 06.07.2012
comment
@ user600029: Если вы удалите свой конструктор копирования, то неявный скопирует каждый член объекта - во многих случаях это именно то, что вам нужно. И если вы удалите его (и оператор присваивания и не объявите свой собственный конструктор перемещения или оператор присваивания перемещения), то компилятор также предоставит вам конструктор перемещения. - person Mike Seymour; 06.07.2012