Почему здесь нужен конструктор копирования? - PullRequest
5 голосов
/ 30 марта 2011

Рассмотрим следующий код:

struct S
{
    S() {}
    void f();
private:
    S(const S&);
};

int main()
{
    bool some_condition;
    S my_other_S;
    (some_condition ? S() : my_other_S).f();
    return 0;
}

gcc не может скомпилировать это, говоря:

test.cpp: In function 'int main()':
test.cpp:6:5: error: 'S::S(const S&)' is private
test.cpp:13:29: error: within this context

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

if (some_condition)
    S().f();
else
    my_other_S.f();

Что отличается в первом случае и почему требуется конструктор копирования?

РЕДАКТИРОВАТЬ : Тогда есть ли способ выразить выражение "выполнить эту операцию над временным объектом над существующим объектом" в контексте выражения?

Ответы [ 4 ]

9 голосов
/ 30 марта 2011

Результатом ?: является rvalue, новый объект, если один из аргументов является rvalue.Чтобы создать это значение, компилятор должен скопировать любой результат.

if (some_condition)
    S().f(); // Compiler knows that it's rvalue
else
    my_other_S.f(); // Compiler knows that it's lvalue

Это по той же причине, по которой вы не можете сделать

struct B { private: B(const B&); };
struct C { C(B&); C(const B&); };
int main() {
    B b;
    C c(some_condition ? b : B());
}

Я изменил свой пример, потому чтостарый был немного отстой.Здесь вы можете ясно увидеть, что нет способа скомпилировать это выражение, потому что компилятор не может знать, какой конструктор вызывать.Конечно, в этом случае компилятор может привести оба аргумента к const B&, но по какой-то причине, которая не очень актуальна, это не будет.

Редактировать: Нет, нет, потому что естьнет способа скомпилировать это выражение, так как важные данные о нем (rvalue или lvalue) изменяются во время выполнения.Компилятор пытается решить эту проблему для вас путем преобразования в rvalue путем создания копии, но не может, потому что не может копировать, поэтому не может компилироваться.

7 голосов
/ 30 марта 2011

С [expr.cond] (формулировка из черновика n3242):

В противном случае, если второй и третий операнды имеют разные типы и имеют либо (возможно, cv-квалифицированный) тип класса, либо если обаДля glvalues ​​той же категории значений и того же типа, за исключением cv-квалификации, делается попытка преобразовать каждый из этих операндов в тип другого.Процесс определения того, может ли выражение операнда E1 типа T1 быть преобразовано для соответствия выражению операнда E2 типа T2, определяется следующим образом:

  • Если E2является lvalue: E1 может быть преобразовано в соответствие E2, если E1 может быть неявно преобразовано (пункт 4) в тип «lvalue reference to T2», с учетом ограничения, что в преобразовании ссылка должнапривязать напрямую (8.5.3) к lvalue.
  • Если E2 является значением xvalue: E1 может быть преобразовано в соответствие E2, если E1 может быть неявно преобразовано в тип «ссылка на rvalueдо T2 ”, при условии ограничения, что ссылка должна связываться напрямую.
  • Если E2 является r-значением или если ни одно из приведенных выше преобразований не может быть выполнено и хотя бы один из операндовимеет (возможно, cv-квалифицированный) тип класса:

    • , если E1 и E2 имеют тип класса, и базовые типы классов совпадают или один является базовым классом другого: E1 может быть соnverted для соответствия E2, если класс T2 того же типа или базовый класс, что и класс T1, а квалификация cv T2 такая же квалификация cv, как иликвалификация cv больше, чем квалификация cv T1.Если преобразование применяется, E1 заменяется на значение типа T2 путем инициализации копии типа T2 из E1 и использования этого временного преобразованного операнда.

Это правило упоминает инициализацию копирования, но оно не применяется, поскольку оба операнда имеют одинаковый тип

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

Это правило не применяется, поскольку S() - это значение, а my_other_S - это значение.

В противном случае результатом является значение типа prvalue.Если второй и третий операнды не имеют одинаковый тип и оба имеют (возможно, cv-квалифицированный) тип класса, разрешение перегрузки используется для определения преобразований (если они есть), которые должны применяться к операндам (13.3.1.2, 13.6),Если не удается разрешить перегрузку, программа работает некорректно.В противном случае применяются определенные таким образом преобразования, а преобразованные операнды используются вместо исходных операндов в оставшейся части этого раздела.Стандартные преобразования Lvalue-to-rvalue (4.1), массива-указателя (4.2) и функции-указателя (4.3) выполняются для второго и третьего операндов.После этих преобразований должно выполняться одно из следующего:

  • Второй и третий операнды имеют одинаковый тип;результат такого типа.Если операнды имеют тип класса, , результатом является временное значение prvalue типа результата, которое инициализируется копией из второго или третьего операнда в зависимости от значения первого операнда.

Это правило применяется, результат инициализируется копией (выделено мной).

4 голосов
/ 30 марта 2011

Это старая известная проблема. Смотри здесь

http://www.open -std.org / ОТК1 / SC22 / wg21 / документы / cwg_defects.html # 446

Согласно решениям комитета, в вашем примере оператор ?: всегда должен возвращать временный объект, что означает, что для ветви my_other_s необходимо скопировать исходный объект my_other_s. Вот почему компилятору требуется конструктор копирования.

Этого языка еще нет в C ++ 03, но многие компиляторы реализовали этот подход с самого начала.

1 голос
/ 30 марта 2011

Что касается вашего обновленного вопроса, если разрешено изменение определения S, может помочь следующий обходной путь:

struct S
{
    ...
    S& ref() { return *this; } // additional member function
    ...
};

(some_condition ? S().ref() : my_other_S).f();

Надеюсь, это поможет

...