Перегрузка по ссылке, по сравнению с передачей по отдельности + std :: move? - PullRequest
48 голосов
/ 15 мая 2010

Кажется, что главный совет относительно значений C ++ 0x состоит в добавлении конструкторов перемещения и операторов перемещения в ваши классы, пока компиляторы не выполнят их по умолчанию.

Но ожидание - проигрышная стратегия, если вы используете VC10, потому что автоматическая генерация, вероятно, не будет здесь до VC10 SP1 или, в худшем случае, до VC11. Вероятно, ожидание этого будет измеряться годами.

Здесь кроется моя проблема. Написание всего этого дублирующего кода не доставляет удовольствия. И на это неприятно смотреть. Но это бремя хорошо воспринято, потому что эти занятия считаются медленными. Не так для сотен, если не тысяч, более мелких классов.

:: sighs :: C ++ 0x должен был позволить мне написать меньше кода, не больше!

А потом у меня возникла мысль. Я думаю, что многие разделяют их.

Почему бы просто не передать все по значению? Разве std :: move + copy elision не сделает это почти оптимальным?

Пример 1 - Типичный конструктор Pre-0x

OurClass::OurClass(const SomeClass& obj) : obj(obj) {}

SomeClass o;
OurClass(o);            // single copy
OurClass(std::move(o)); // single copy
OurClass(SomeClass());  // single copy

Минусы: Потерянная копия для значений.

Пример 2 - Рекомендуемый C ++ 0x?

OurClass::OurClass(const SomeClass& obj) : obj(obj) {}
OurClass::OurClass(SomeClass&& obj) : obj(std::move(obj)) {}

SomeClass o;
OurClass(o);            // single copy
OurClass(std::move(o)); // zero copies, one move
OurClass(SomeClass());  // zero copies, one move

Плюсы: Предположительно, самый быстрый.
Минусы: Много кода!

Пример 3 - передача по значению + std :: move

OurClass::OurClass(SomeClass obj) : obj(std::move(obj)) {}

SomeClass o;
OurClass(o);            // single copy, one move
OurClass(std::move(o)); // zero copies, two moves
OurClass(SomeClass());  // zero copies, one move

Плюсы: Без дополнительного кода.
Минусы: Потерянный ход в случаях 1 и 2. Производительность сильно снизится, если у SomeClass нет конструктора хода.


Что ты думаешь? Это правильно? Является ли понесенный шаг общеприемлемой потерей по сравнению с преимуществами сокращения кода?

1 Ответ

7 голосов
/ 09 июня 2010

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

Сначала твой вздох.

:: sighs :: C ++ 0x должен был позволить мне писать меньше кода, а не больше!

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

OurClass::OurClass(SomeClass&& obj) : obj(std::move(obj)) {}

Лично я предпочитаю многословие в сложных и важных ситуациях, потому что это предупреждает меня и возможных читателей моего кода.

возьмите, например, приведение в стиле C (T*)pT и стандарт C ++ static_cast<T*>(pT) Гораздо более многословный - но большой шаг вперед.

Во-вторых, я немного подозрительно относился к вашему Примеру 3, последнему тестовому примеру. Я подумал, что может быть другой конструктор перемещения, задействованный для создания переданного по значению параметра из значения rvalue. Итак, я создал небольшой проект в своем новом VS2010 и получил некоторые разъяснения. Я опубликую здесь код и результаты.

источник:

// test.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include <utility>
#include <iostream>

class SomeClass{
    mutable int *pVal;
public:
    int Val() const { return *pVal; };
    SomeClass(int val){
        pVal = new int(val);
        std::cout << "SomeClass constructor(pVal = 0x" << std::hex << pVal << std::dec << ")" << std::endl;
    }
    SomeClass(const SomeClass& r){
        pVal = new int(r.Val());
        std::cout << "SomeClass copy constructor(pVal = 0x" << std::hex << pVal << std::dec << ")" << std::endl;
    }   
    SomeClass(const SomeClass&& r){
        pVal = r.pVal;
        r.pVal = 0;
        std::cout << "SomeClass move constructor(pVal = 0x" << std::hex << pVal << std::dec << ")" << std::endl;
    }
    ~SomeClass(){
        if(pVal)
            delete pVal;
        std::cout << "SomeClass destructor(pVal = 0x" << std::hex << pVal << std::dec << ")" << std::endl;
    }
};

class OtherClass{
    SomeClass sc;
public:
    OtherClass(int val):sc(val){
    }

Обратите внимание на этот раздел:

#if 1
    OtherClass(SomeClass r):sc(std::move(r)){
    }
#else
    OtherClass(const SomeClass& r):sc(r){
    }   
    OtherClass(const SomeClass&& r):sc(std::move(r)){
    }
#endif

...

    int Val(){ return sc.Val(); }
    ~OtherClass(){
    }
};

#define ECHO(expr)  std::cout << std::endl << "line " << __LINE__ << ":\t" #expr ":" << std::endl; expr

int _tmain(int argc, _TCHAR* argv[])
{
    volatile int __dummy = 0;
    ECHO(SomeClass o(10));

    ECHO(OtherClass oo1(o));            
    __dummy += oo1.Val();
    ECHO(OtherClass oo2(std::move(o))); 
    __dummy += oo2.Val();
    ECHO(OtherClass oo3(SomeClass(20)));  
    __dummy += oo3.Val();

    ECHO(std::cout << __dummy << std::endl);
    ECHO(return 0);
}

Как вы заметили, есть переключатель времени компиляции, который позволяет мне протестировать два подхода.

Результаты лучше всего просматривать в режиме сравнения текста, слева вы можете увидеть компиляцию #if 1, что означает, что мы проверяем предложенный обходной путь, а справа - #if 0, что означает, что мы проверяем «кошерный» способ, описанный в c ++ 0x!

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

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

В любом случае я не ухожу от того, что лучше написать еще один конструктор в классе оболочки. Это всего лишь несколько строк, так как вся утомительная работа в SomeClass, которую имеет , имеет конструктор перемещения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...