RVO / NRVO и открытый неопределенный конструктор копирования - PullRequest
9 голосов
/ 05 апреля 2011

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

Что у нас было:

#include <vector>

class T;

class C
{
public:
    C() { }
    ~C( ) { /*something non-trivial: say, calls delete for all elements in v*/ }
    // a lot of member functions that modify C
    // a lot of member functions that don't modify C
private:
    C(C const &);
    C& operator=(C const&);
private:
    std::vector< T* > v;
};

void init(C& c) { } // cannot be moved inside C

// ...
int main()
{
    // bad: two-phase initialization exposed to the clients
    C c;
    init(c);

    // bad: here follows a lot of code that only wants read-only access to c
    //      but c cannot be declared const
}

Чтобыло предложено:

#include <vector>

class T;

class C
{
public:
    C() { }
    ~C( ) { /*calls delete for all elements in v*/ }

    // MADE PUBLIC
    C(C const &); // <-- NOT DEFINED

    // a lot of member functions that modify C
    // a lot of member functions that don't modify C
private:
    C& operator=(C const&);
private:
    vector< T* > v;
};

C init() // for whatever reason object CANNOT be allocated in free memory
{
    C c;
    // init c
    return c;
}

// ...
int main()
{
    C const & c = init();
}

Это компилирует и связывает (и работает), используя последние g ++ (который является единственным целевым компилятором) и 4.1.2 и 4.4.5 - из-за (N) RVO копииконструктор никогда не вызывается;Деструктор вызывается только в конце main ().

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

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

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

Пожалуйста, не размещайте здесь учебник C ++:

  • Мне известно о «Правиле трех» и я прочитал 12.8 / 15 и 12.2 Священного стандарта;
  • Я не могу использовать ни vector<shared_ptr<T> >, ни ptr_vector<T>;
  • Я не могу выделить C в свободной памяти и вернуть его из init через C*.

Спасибо.

Ответы [ 3 ]

10 голосов
/ 05 апреля 2011

Это компилирует и связывает (и работает), используя недавний g ++ (который является единственным целевым компилятором) и 4.1.2 и 4.4.5 - из-за (N) RVO конструктор копирования никогда не вызывается;Деструктор вызывается только в конце main ().

Хотя он может работать с GCC, ваш код действительно имеет неопределенное поведение, поскольку ссылается на функцию, которая не определена.В таком случае ваша программа плохо сформирована;Диагностика не требуется.Это означает, что GCC может игнорировать нарушение правила, но другие компиляторы могут диагностировать его или сделать что-то еще странное.

Так что на этом основании я бы отклонил этот способ.недостаточно аргументации, поэтому сейчас я ищу технические детали.

Вы хотите использовать семантику перемещения здесь.Как насчет этого явного?

class T;
class C;

struct CMover {
  C *c;
private:
  CMover(C *c):c(c) { }
  friend CMover move(C &c);
};

class C {
public:
    C() { }
    ~C( ) { /*calls delete for all elements in v*/ }

    C(CMover cmove) {
      swap(v, cmove.c->v);
    }

    inline operator CMover();

    // a lot of member functions that modify C
    // a lot of member functions that don't modify C
private:
    C& operator=(C const&); // not copy assignable
    C(C &); // not lvalue copy-constructible

private:
    vector< T* > v;
};

CMover move(C &c) { return CMover(&c); }
C::operator CMover() { return move(*this); }

Теперь вы можете сказать

C init() // for whatever reason object CANNOT be allocated in free memory
{
    C c;
    return move(c);
}

int main() {
  C const c(init());
}
0 голосов
/ 05 апреля 2011

Это похоже на то, что вы не получите из головы людей только по техническим причинам (иначе «Но это компилируется и работает на нашем компиляторе!»), Так что, возможно, концептуально более простой подход может быть хорошей идеей?

Если тебя беспокоит постоянство ...

C c;
init(c);

// bad: here follows a lot of code that only wants read-only access to c
//      but c cannot be declared const

против

C const & c = init();

самое простое (как в случае: хаки и прокси не требуется) решение может быть:

C c_mutable_object;
init(c_mutable_object);
C const& c = c_mutable_object;

// ... lots of code with read-only access to c
0 голосов
/ 05 апреля 2011

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

Скорее всего, если он скомпилируется на конкретном компиляторе, он будет функционировать, как и ожидалось.

Я знаю, что на то, что вы можете сделать, существует масса, казалось бы, произвольных / искусственных ограничений, но можете ли вы использовать прокси-держатель для C?

class C
{
private:
    C() { }
    ~C( ) { /*calls delete for all elements in v*/ }

    // ...
    friend class C_Proxy;
};

class C_Proxy
{
public:
    C_Proxy() : c_() { init(c_); }

    // Or create a public const& attribute to point to this.
    const C& get_C() const { return c_; }

private:
    C c_;
};
...