Конструктор копирования C ++, вызывающий некомпилированный код (gcc) - PullRequest
2 голосов
/ 05 октября 2009

У меня есть следующий код, который не компилируется. Ошибка компилятора:

"error: no matching function to call B::B(B)",
candidates are B::B(B&) B::B(int)"

Код компилируется при одном из следующих двух условий:

  1. Раскомментирование функции B (const B &)
  2. заменить 'main' на следующее

    int main()
    {
            A a;
            B b0;
            B b1 = b0;
            return 0;
    }
    

Если я сделаю 1, код скомпилируется, но в выводе говорится, что он вызывает «неконстантный конструктор копирования».

Может кто-нибудь сказать мне, что здесь происходит?

using namespace std;
class B
{
    public:
    int k;
     B()
     { 
        cout<<"B()"<<endl; 
     }
     B(int k) 
     { 
        cout<<"B(int)"<<endl;
        this->k = k;
     }
     /*B(const B& rhs) { 
        cout<<"Copy constructor"<<endl;
        k = rhs.k;
     }*/
     B(B& rhs) 
     { 
        cout<<"non const Copy constructor"<<endl;
        k = rhs.k;
     }
     B operator=(B& rhs)
     {
        cout<<"assign operator"<<endl;
        k = rhs.k;
        return *this;
     }  
};

class A
{
    public:
        B get_a(void)
        {
            B* a = new B(10);
            return *a;
        }       
};

int main()
{
    A a;
    B b0 = a.get_a();  // was a.just();
    B b1 = b0 ;
    return 0;
}

Ответы [ 4 ]

7 голосов
/ 05 октября 2009

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

В вашем случае, GCC достаточно умен, чтобы избежать одной из двух копий. Чтобы свести ваш код к более простому примеру

B returnB()
{
    B a;
    B* b = &a;
    return *b;
}

int main()
{
    B c = returnB();
    return 0;
}

Если следовать стандарту и не выполнять RVO, две копии создаются в процессе создания c - копии *b в возвращаемое значение returnB и копии возвращаемого значения в c сам. В вашем случае GCC пропускает первую копию и вместо этого делает только одну копию из *b непосредственно в c. Это также объясняет, почему B(B&) вызывается вместо B(const B&) - поскольку *b (он же a) не является временным значением, компилятору больше не нужно использовать B(const B&), а вместо этого выбирается более простой * Вместо этого вызывайте 1023 * при построении c (перегрузка не const всегда автоматически предпочтительнее перегрузки const, если выбор существует).

Так почему же компилятор все еще выдает ошибку, если B(const B&) нет? Это потому, что синтаксис вашего кода должен быть правильным, прежде чем можно будет выполнить оптимизацию (например, RVO). В приведенном выше примере returnB() - это , возвращающий временный (согласно правилам синтаксиса C ++), поэтому компилятор должен видеть конструктор копирования B(const B&). Однако, как только ваш код подтвердит грамматическую корректность компилятора, он сможет выполнить оптимизацию так, чтобы B(const B&) никогда не использовался.

РЕДАКТИРОВАТЬ : Шляпа для Чарльза Бейли, который нашел следующее в стандарте C ++

12.2 [class.teven]: «Даже когда избегают создания временного, все семантические ограничения должны быть уважаемый, как будто временный объект был создан. "

, который только усиливает и подтверждает необходимость конструктора копирования, принимающего ссылку на const, когда временные объекты должны быть скопированы для построения (независимо от того, действительно ли используется конструктор)

3 голосов
/ 05 октября 2009

Ваша функция get_a() возвращает объект B, а не ссылку (но утечка вновь созданного объекта B). Кроме того, чтобы назначения работали так, как вы делаете, вам нужно убедиться, что ваш оператор присваивания и конструкторы копирования принимают const B& аргументы - тогда B b1 = b0 будет работать в этом случае. Это работает:

class B
{
public:
  int k;
  B() { cout<<"B()"<<endl; }
  B(int k) { 
    cout<<"B(int)"<<endl;
    this->k = k;
  }
  B(const B& rhs) { 
    cout<<"non const Copy constructor"<<endl;
    k = rhs.k;
  }
  B operator=(const B& rhs) {
    cout<<"assign operator"<<endl;
    k = rhs.k;
    return *this;
  }
};

class A {
public:
  B* get_a(void) {
    B* a = new B(10);
    return a;
  }
  B get_a2(void) {
    B a(10);
    return a;
  }
};

int main() {
  A a;
  B b0 = *a.get_a(); // bad: result from get_a() never freed!
  B b1 = a.get_a2(); // this works too
  return 0;
}
1 голос
/ 05 октября 2009

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

Длинное объяснение: Чтобы понять, почему ваш код не компилируется, если есть только «конструктор неконстантного копирования» 1 , вам необходимо ознакомиться с терминами lvalue и Rvalue . Изначально они означали, что rvalue s может только стоять справа от оператора = (назначение), в то время как lvalue s может стоять также слева. Пример:

T a, b;
const T ac;
a = b; // a can appear on the left of = 
b = a; // so can b => a and b are lvalues in this context
ac = a; // however, ac is const so assignment doesn't make sense<sup>2</sup>, ac is a rvalue

Когда компилятор выполняет разрешение перегрузки (обнаруживая, какая перегрузка функции / метода лучше всего соответствует предоставленным аргументам), он позволяет lvalue s сопоставлять параметры, передаваемые значением 3 , ссылочные и константные ссылочные типы. Однако он будет соответствовать rvalue s только со значением 3 и константными опорными параметрами. И это потому, что в некотором смысле, поскольку rvalue s не может быть помещено в левую часть оператора =, они имеют семантику только для чтения, и когда нельзя изменять их. И когда вы принимаете параметр через неконстантную ссылку, подразумевается, что вы каким-то образом измените этот параметр.

Последний кусочек головоломки: временные объекты значение с. Функция, возвращаемая по значению, создает временный объект с очень ограниченным сроком службы. Из-за своей ограниченной продолжительности жизни он считается const, и поэтому является rvalue . И это rvalue не сопоставляет функции с параметрами по неконстантной ссылке. Примеры:

void f_cref(const A& a) { std::cout << "const ref" << std::endl; }
void f_ref(A& a) { std::cout << "non-const ref" << std::endl; }

A geta() { return A(); }

A a;
const A ac;
f_ref(a); // ok, a is a lvalue
f_ref(ac); // error, passing const to non-const - rvalue as lvalue - it's easy to spot here
f_cref(a); // ok, you can always pass non-const to const (lvalues to rvalues)
f_ref(geta()); // error, passing temporary and therefore const object as reference
f_cref(geta()); // ok, temporary as const reference

Теперь у вас есть вся информация, чтобы выяснить, почему ваш код не компилируется. Конструктор копирования похож на обычные функции.

Я немного упростил вещи, так что лучшее, более полное и правильное объяснение можно найти в этом превосходном сообщении в блоге команды разработчиков Visual C ++ Studio о ссылках на rvalue , которое также затрагивает новую функцию C ++ 0x "rvalue" ссылки "

1 - нет такого понятия, как неконстантный конструктор копирования. Конструктор копирования принимает константную ссылку, точка.

2 - вы, вероятно, можете поместить объект const слева от =, если у него есть оператор = объявленный const. Но это было бы ужасно, ужасно, бессмысленно делать.

3 - на самом деле вы не сможете передать const A по значению, если A не имеет конструктора копирования - тот, который принимает const A &, то есть.

1 голос
/ 05 октября 2009

Линия B b1 = b0; является виновником. Эта строка требует вызова конструктора копирования. Вы можете исправить код, написав вместо него B b1(b0) или определив конструктор копирования, который принимает const B&, но не B&.

...