2009-04-27 2 views
4

Я написал небольшую тестовую программу с классом образцов, содержащую также самоопределяемый конструктор, деструктор, конструктор копирования и оператор присваивания. Я был удивлен, когда я понял, что конструктор копирования не был вызван вообще, хотя я реализовал функцию со значениями возвратных моего класса и линии, как Object o1; Object o2(o1);Как заставить компилятор использовать явный конструктор копирования?

innerclass.hpp:

#include <iostream> 

class OuterClass 
{ 
public: 
OuterClass() 
{ 
    std::cout << "OuterClass Constructor" << std::endl; 
} 
~OuterClass() 
{ 
    std::cout << "OuterClass Destructor" << std::endl; 
} 
OuterClass(const OuterClass & rhs) 
{ 
    std::cout << "OuterClass Copy" << std::endl; 
} 
OuterClass & operator=(const OuterClass & rhs) 
{ 
    std::cout << "OuterClass Assignment" << std::endl; 
} 

class InnerClass 
{ 
public: 
    InnerClass() : m_int(0) 
    { 
     std::cout << "InnerClass Constructor" << std::endl; 
    } 
    InnerClass(const InnerClass & rhs) : m_int(rhs.m_int) 
    { 
     std::cout << "InnerClass Copy" << std::endl; 
    } 
    InnerClass & operator=(const InnerClass & rhs) 
    { 
     std::cout << "InnerClass Assignment" << std::endl; 
     m_int = rhs.m_int; 
     return *this; 
    } 
    ~InnerClass() 
    { 
     std::cout << "InnerClass Destructor" << std::endl; 
    } 
    void sayHello() 
    { 
     std::cout << "Hello!" << std::endl; 
    } 

private: 
    int m_int; 
}; 

InnerClass innerClass() 
{ 
    InnerClass ic; 
    std::cout << "innerClass() method" << std::endl; 
    return ic; 
} 
}; 

innerclass.cpp:

#include "innerclass.hpp" 

int main(void) 
{ 
std::cout << std::endl << "1st try:" << std::endl; 


OuterClass oc; 
OuterClass oc2(oc); 
oc.innerClass().sayHello(); 

std::cout << std::endl << "2nd try:" << std::endl; 

OuterClass::InnerClass ic(oc.innerClass()); 
ic = oc.innerClass(); 
} 

Выход:

1st try: 
OuterClass Constructor 
OuterClass Copy 
InnerClass Constructor 
innerClass() method 
Hello! 
InnerClass Destructor 

2nd try: 
InnerClass Constructor 
innerClass() method 
InnerClass Constructor 
innerClass() method 
InnerClass Assignment 
InnerClass Destructor 
InnerClass Destructor 
OuterClass Destructor 
OuterClass Destructor 

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

Или, еще лучше, есть ли способ заставить компилятор использовать самоопределяемый конструктор копирования?

+0

Можете ли вы связать с тем, что вы читаете? Я подозреваю, что вы что-то недопонимаете. –

+0

Опубликовать код. Нам нужно увидеть, как вы определили конструктор копирования. –

+0

Уверен, что o1 неявно не преобразуется в какой-либо другой тип, который Object можно построить? Без конкретного кода нет реального способа узнать, почему ваш код будет терпеть неудачу. –

ответ

6

Просто для полноты с другими ответами, стандарт позволяет компилятор опускает копию конструктор в определенных ситуациях (другие ответы называются «Оптимизация возвращаемых значений» или «Оптимизация с наименьшими возвращаемыми значениями» - RVO/NRVO):

12.8 объектов класса Копирование, пункт 15 (C++ 98)

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

Так что в вашем методе innerClass(), конструктор копирования можно подумать, будет называться по возвращении разрешается оптимизировать расстояние:

InnerClass innerClass() { 
    InnerClass ic; 
    std::cout << "innerClass() method" << std::endl; 
    return ic; // this might not call copy ctor 
} 
+0

+1 Просто добавьте, это противоречиво w.r.t. аспект «побочных эффектов». Как вы видели, результат вашей программы изменяется этой оптимизацией. Юк! –

4

Вы не должны проектировать свой класс с опорой на конструктор копирования, вызываемый (или не вызываемый) при определенных обстоятельствах. Компилятору разрешено удалять или добавлять вызовы конструктора вызовов во всевозможные места, и вы действительно не хотите отслеживать их.

Что касается того, зачем вам нужно - ну, компилятор может решить, что ему нужна копия, а конструктор копирования - это то, что он использует для этого. Преимущество вызова конструктора копирования - это производительность - копирование обычно довольно дорогостоящее.

+0

Насколько я понимаю, вы правы ... конструктор копирования может быть вызван и, возможно, нет. Но * если * один необходим, он всегда будет без исключения быть явно определенным и * не * что-либо автоматически генерируемое компилятором? – Chris

+0

Это правильно. – 2009-04-27 14:00:27

+1

Другими словами, не помещайте побочные эффекты в свои конструкторы копирования, так как у компилятора есть свобода, чтобы избежать вызовов к ним в определенных ситуациях. –

0

Object o1(); не создает никаких объектов, а определяет прототип функции с именем функции o1, void arguments и возвращаемым типом как объект. Вам нужно опубликовать код, чтобы найти актуальную проблему.

+0

Хорошая точка! Это «самый неприятный синтаксический анализ на C++», как его называют в книге «Скот Майерс Эффектив-СТЛ». Другой пример такой вводящей в заблуждение конструкции: list данные (istream_iterator (dataFile), istream_iterator ()); – 2009-04-27 13:58:53

+0

Это была опечатка, извините за это. – Chris

4

Я согласен с Нилом, вы не должны писать класс, который зависит от вызываемого конструктора копии. А именно потому, что компилятор может делать такие вещи, как «Именованная оптимизация возвращаемых значений» (link), которая полностью избегает конструктора копирования во многих сценариях возвращаемого значения. Чтобы принудительно вызвать конструктор копирования, вам нужно написать много кода, предназначенного для «обмана» компилятора C++. Не хорошая идея.

В конкретном сценарии, хотя если вы хотите принудительно вызвать вызов конструктора копирования, вы можете сделать явный вызов.

Object SomeFunc() { 
    Object o1 = ... 
    return Object(o1); 
} 
+0

Спасибо за ваш ответ. Правильно ли, что с вашим решением возможно, что конструктор копирования вызывается дважды? В первый раз, когда вы вызываете Object (o1) и второй раз действительно возвращаете значение? – Chris

+0

@ Крис, да, это возможно. Оптимизация возвращаемого значения является частью компилятора C++, с которым я не знаком, поэтому я не могу с уверенностью сказать да или нет двойной копии. Однако я верю *. – JaredPar

+0

Однако, поймите, что копия ctor может быть вызвана дважды в этой ситуации (если компилятор решает не выполнять RVO). Например, VC9 без оптимизаций дважды вызывает копию ctor, если я изменяю return в методе innerClass(), чтобы возвращать InnerClass (ic); ' –

0

Опубликовать код. Я думаю, вы используете неправильный синтаксис.

Конструктор копирования должен точно иметь следующую подпись:

MyObject(const MyObject&) 

Затем вы можете увидеть, если конструктор копирования вызывается с помощью следующего кода:

MyObject m1; 
MyObject m2(m1); 

Вы не разрешается использовать MyObject m1(); это объявление функции.

+0

Вот что я сделал. – Chris

2

В этом проблема?

OuterClass(const OuterClass & rhs) 
{   
std::cout << "OuterClass Constructor" << std::endl; 
==> 
std::cout << "OuterClass Copy Constructor" << std::endl; 

} 

OuterClass & operator=(const OuterClass & rhs) 
{  
std::cout << "OuterClass Constructor" << std::endl; 
==> 
std::cout << "OuterClass Assignment operator" << std::endl; 
} 

Ошибка копирования в пачку!

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

EDIT: для внутреннего выпуска класса:

Как и другие уже отмечали это дело Имя Возвращаемое значение оптимизации (NRVO).

InnerClass innerClass() 
{  
    InnerClass ic;   
    std::cout << "innerClass() method" << std::endl;   
    return ic; 
} 

компилятор может трансформирует функцию

void innerClass(InnerClass &namedResult) 

{ 
std::cout << "innerClass() method" << std::endl; 

} 

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

Перейди через ниже две ссылки, чтобы понять больше на NRVO:

+0

Спасибо, это действительно была ошибка для «OuterClass». Я не узнал об этом, так как я сосредоточился на «InnerClass», где проблема все еще существует. – Chris

Смежные вопросы