#include <iostream>
class A
{
public:
A() {
std::cout << "+A";
}
~A() {
std::cout << "-A";
}
};
class B : public A
{
public:
B() {
std::cout << "+B";
}
~B() {
std::cout << "-B";
}
};
void func( A a)
{
}
void main()
{
B b;
func(b);
}
Выдача по нему: +A+B-A-A-B-A
При передаче параметра в функцию, по-видимому, вызывается дефолтный копирующий конструктор (в выдачу он не попадает), соответственно, третья -A объясняется вызовом парного деструктора
при выходе из функции. А вот чем объяснить четвёртую -A?
Здравствуйте, andy1618, Вы писали: A>Выдача по нему: +A+B-A-A-B-A A>При передаче параметра в функцию, по-видимому, вызывается дефолтный копирующий конструктор (в выдачу он не попадает), соответственно, третья -A объясняется вызовом парного деструктора A>при выходе из функции. А вот чем объяснить четвёртую -A?
Собрал пример с помощью gcc, вывод: +A+B-A-B-A
Вообще делать так, как написано в примере — плохо, по двум причинам:
— происходит срезка всего того, что относится к производному классу. Когда есть наследование необходимо использовать передачу по ссылке, либо по указателю на базовый класс.
— лишнее копирование. Иногда это оправдано, но передача по const ссылке значительно эффективней.
+A — создали базовый класс в main
+B — создали производный класс в main
не вывели копирующий конструктор
-A — разрушили объект созданный при вызове функции
-B — разрушили производный в main
-A — разрушили базовый в main
Здравствуйте, andy1618, Вы писали:
A>Выдача по нему: +A+B-A-A-B-A A>При передаче параметра в функцию, по-видимому, вызывается дефолтный копирующий конструктор (в выдачу он не попадает), соответственно, третья -A объясняется вызовом парного деструктора A>при выходе из функции. А вот чем объяснить четвёртую -A?
К слову, если переписать код, вот так:
#include <iostream>
class A
{
public:
A() {
std::cout << "+A";
}
A(const A& a){
std::cout << "cpy A";
}
~A() {
std::cout << "-A";
}
};
class B : public A
{
public:
B() {
std::cout << "+B";
}
B(const B& b){
std::cout << "cpy B";
}
~B() {
std::cout << "-B";
}
};
void func( A a)
{
}
int main()
{
B b;
func(b);
return 0;
}
Тогда выводу будет:
+A+Bcpy A-A-B-A
Копировать объект типа B, конструктором копирования класса A, который ничего не знает о B — как раз и есть пример срезки (slicing).
Если же сигнатуру функции func поменять следующим образом:
void func(const A& a)
{
}
Тогда вывод будет:
+A+B-B-A
Никаких лишних копирований и проблем со срезкой объектов.
Clang и GCC при -fno-elide-constructors не делают лишнюю копию.
P.S. обсуждение срезки абсолютно не к месту — далеко не все классы полиморфны, соответственно это не всегда проблема. Более того, иногда нужна именно копия родительского типа(допустим чтобы swap-нуться).
С++03 8.5/12 Initializers. The initialization that occurs in argument passing, function return, throwing an exception (15.1), handling
an exception (15.3), and brace-enclosed initializer lists (8.5.1) is called copy-initialization and is equivalent
to the form T x = a;
То есть
{
B b;
A a = b;
}
Должен дать такой же результат в плане вызова конструкторов, но студия выдаёт "+A+B-A-B-A".
Очевидно (учитывая правила для copy-initialization, и сигнатуру implicitly-declared copy constructor) , что в этом упрощённом случае, не должно быть лишней копии, и раз при func(b) она есть — значит в студии баг.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Ты же в студии тестируешь? EP>Видимо вот так: EP>
EP>func(A(b));
EP>
Так и должно быть, копирующий конструктор вызывается неявным образом. Потому что имеет сигнатуру по умолчанию:
func(const A&);
Создаем A на стеке, как базовый B в main.
Создаем B на стеке, дополняя A в main.
Заносим на стек параметры функции f, для этого конструируем объект типа A с помощью конструктора копирования по ссылке на B.
Выходим из функции, разрушаем объекты в стеке, соответствующие функции f — деструктор A.
Выходим из main, разрушаем объекты в стеке, соответствующие функции main — деструктор B, деструктор A.
Можно более развернутое пояснение, что за код такой собирает Visual C++ ?
EP>Clang и GCC при -fno-elide-constructors не делают лишнюю копию.
Непосредственно с этим флагом не сталкивался, но он работает, только в случаях, когда в функции вызываются константные методы?
Тогда все же логичней использовать const ссылку?
EP>P.S. обсуждение срезки абсолютно не к месту — далеко не все классы полиморфны, соответственно это не всегда проблема. Более того, иногда нужна именно копия родительского типа(допустим чтобы swap-нуться).
Сходу в голову приходит только случай, когда требуется реализовать operator+ через +=, тогда по значению самое-то.
Можете привести более развернутый пример?
Здравствуйте, ak239, Вы писали:
EP>>Clang и GCC при -fno-elide-constructors не делают лишнюю копию. A>Непосредственно с этим флагом не сталкивался, но он работает, только в случаях, когда в функции вызываются константные методы? A>Тогда все же логичней использовать const ссылку?
Нет, не только константные. Это про copy-elision, которая разрешена стандартом.
EP>>P.S. обсуждение срезки абсолютно не к месту — далеко не все классы полиморфны, соответственно это не всегда проблема. Более того, иногда нужна именно копия родительского типа(допустим чтобы swap-нуться). A>Сходу в голову приходит только случай, когда требуется реализовать operator+ через +=, тогда по значению самое-то. A>Можете привести более развернутый пример?
Want Speed? Pass by Value.
Если ты в любом случае внутри функции делаешь копию, то отразив это непосредственно в интерфейсе — открывается возможность для copy elision.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>P.P.S:
EP>Должен дать такой же результат в плане вызова конструкторов, но студия выдаёт "+A+B-A-B-A". EP>Очевидно (учитывая правила для copy-initialization, и сигнатуру implicitly-declared copy constructor) , что в этом упрощённом случае, не должно быть лишней копии, и раз при func(b) она есть — значит в студии баг.
Студия выдает абсолютно адекватный вывод. +A+B /*cpy A(B&)*/ -A-B-A.
Был так заинтригован студией, что немного поэкспериментировал с отладчиком студии.
Пришел к выводу, что вывод: +A+B-A-A+A+B может быть получен только, если студия подставляет реализацию конструктора копирования по умолчанию.
Буду рад, если кто-нибудь подробно объяснит, что за оптимизацию применяет студия.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Want Speed? Pass by Value. EP>Если ты в любом случае внутри функции делаешь копию, то отразив это непосредственно в интерфейсе — открывается возможность для copy elision.
Спасибо за ссылку.
Появилась идея, возможно, Visual C++ вначале преобразует тип B к типу A, а потом копирует A в стек функции f. Отсюда и берется два деструктора.
Здравствуйте, ak239, Вы писали:
EP>>P.P.S: EP>>Должен дать такой же результат в плане вызова конструкторов, но студия выдаёт "+A+B-A-B-A". EP>>Очевидно (учитывая правила для copy-initialization, и сигнатуру implicitly-declared copy constructor) , что в этом упрощённом случае, не должно быть лишней копии, и раз при func(b) она есть — значит в студии баг. A>Студия выдает абсолютно адекватный вывод. +A+B /*cpy A(B&)*/ -A-B-A.
Студия выдаёт +A+B-A-A-B-A, которого не должно быть.
Дальнейшие эксперименты показали, что копию можно поймать добавив какой-нибудь POD в A (если добавлять что-то с явным конструктором копирования — то лишняя копия пропадает).
#include <iostream>
class A
{
public:
__declspec(noinline) A() {
std::cout << "+A";
}
__declspec(noinline) ~A() {
std::cout << "-A";
}
char a[100];
};
class B : public A
{
public:
__declspec(noinline) B() {
std::cout << "+B";
}
__declspec(noinline) ~B() {
std::cout << "-B";
}
};
__declspec(noinline) void func(A a)
{
(void)a;
}
int main()
{
B b;
func(b);
// BTW, if use:
// const A &a=b;
// func(a);
// then superfluous copy disapears
}
Тут видно что делаются две копии.
Кстати, первая копия — T24646 — вообще не при делах. Вторая копия делается тоже с b.
Возможно это оптимизатор заменил
Здравствуйте, ak239, Вы писали:
A>Появилась идея, возможно, Visual C++ вначале преобразует тип B к типу A, а потом копирует A в стек функции f. Отсюда и берется два деструктора.
Здравствуйте, andy1618, Вы писали:
A>Выдача по нему: +A+B-A-A-B-A A>... A>при выходе из функции. А вот чем объяснить четвёртую -A?
Если в таких примерах завести привычку распечатывать не только +/- А/В, но и this, то есть адрес объекта, то разбираться что где и зачем случилось проще...
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>Здравствуйте, ak239, Вы писали:
A>>Появилась идея, возможно, Visual C++ вначале преобразует тип B к типу A, а потом копирует A в стек функции f. Отсюда и берется два деструктора.
EP>Так я про это и говорил в первом же сообщении
Просто посчитал это вызовом конструктора копирования.
func((A)b);
Было бы понятней.
Но, все-таки, почему студия пытается преобразовать тип, забывая про конструктор копирования по умолчанию, а gcc использует конструктор по умолчанию.
Кто прав согласно стандарту? Как должен вести себя компилятор, пытаться сконструировать объект, используя конструктор копирования, либо пытаться преобразовать объект.
И почему поведение меняется при определении конструктора копирования.
Здравствуйте, ak239, Вы писали:
A>Кто прав согласно стандарту? Как должен вести себя компилятор, пытаться сконструировать объект, используя конструктор копирования, либо пытаться преобразовать объект.