struct C;
struct B
{
B() {}
B(const B &) { std::cout << "copy constructor" << std::endl; }
B(const C &) { std::cout << "[cencored] constructor" << std::endl; }
};
--
ПК
_>Тип C, третий случай: _>
_>struct C: B {};
_>
_>Вывод GCC 3.3.1, IMHO, правильно: _>
_>copy constructor
_>
_>Вывод VC 7.1, IMHO, неправильно, бред какой то:
1. а не бред объявит в базовом классе конструктор, принимающий на вход экземпляр производного класса ?
2. компилятор естественно вызывает конструктор B(C), т.к. ему в этом случае не нужно преобразования типа (т.е. его поведение вполне соответствует стандарту), а если хочется другого (не стандартного)поведения, то сделай сам явное преобразование (твой пример выше это и демонстрирует). а вот поведение GCC мягко говоря некоректно.
Здравствуйте, Bork, Вы писали:
B>1. а не бред объявит в базовом классе конструктор, принимающий на вход экземпляр производного класса ?
Гм... Мининимальный пример показывающий ошибку в компиляторе не являеться бредом, если он показывает ошибку в компиляторе. Искать какую то другую смысловую нагрузку в таком примере можно, но это offtopic.
B>2. компилятор естественно вызывает конструктор B(C), т.к. ему в этом случае не нужно преобразования типа (т.е. его поведение вполне соответствует стандарту),
Не соответствует стандарту. См. стандарт:
12.8.8.
The implicitly-defined copy constructor for class X performs a memberwise copy of its subobjects. The
order of copying is the same as the order of initialization of bases and members in a user-defined constructor
(see 12.6.2). Each subobject is copied in the manner appropriate to its type:
— if the subobject is of class type, the copy constructor for the class is used;
— if the subobject is an array, each element is copied, in the manner appropriate to the element type;
— if the subobject is of scalar type, the built-in assignment operator is used.
Здравствуйте, sergey_shandar, Вы писали:
_>Не соответствует стандарту. См. стандарт: _>
_>12.8.8.
_>The implicitly-defined copy constructor for class X performs a memberwise copy of its subobjects. The
_>order of copying is the same as the order of initialization of bases and members in a user-defined constructor
_>(see 12.6.2). Each subobject is copied in the manner appropriate to its type:
_>— if the subobject is of class type, the copy constructor for the class is used;
_>— if the subobject is an array, each element is copied, in the manner appropriate to the element type;
_>— if the subobject is of scalar type, the built-in assignment operator is used.
хехе ключевое слово в этой выдержке — "The implicitly-defined copy constructor". А где в своем примере ты видишь это implicitly-defined ? если у тебя определен пользовательский конструктор B(C), который подходит к ситуации лучше, т.к. не требует приведения типов.
Здравствуйте, Bork, Вы писали:
B>хехе ключевое слово в этой выдержке — "The implicitly-defined copy constructor". А где в своем примере ты видишь это implicitly-defined ? если у тебя определен пользовательский конструктор B(C), который подходит к ситуации лучше, т.к. не требует приведения типов.
См. пример внимательно:
struct C;
struct B
{
B() {}
B(const B &) { std::cout << "copy constructor" << std::endl; }
B(const C &) { std::cout << "[cencored] constructor" << std::endl; }
};
struct C: B {}; ///< строка 8.
В строке 8 компилятор должен создавать правильный "The implicitly-defined copy constructor", который должен вызывать для каждого "of its subobjects" "the copy constructor", т.е. B::B(const B &). А вызываеться B(const C &), т.е. бред какой то, ч.т.д.
_>В строке 8 компилятор должен создавать правильный "The implicitly-defined copy constructor", который должен вызывать для каждого "of its subobjects" "the copy constructor", т.е. B::B(const B &). А вызываеться B(const C &), т.е. бред какой то, ч.т.д.
это с какой такой великой радости он это должен ?
в строке 8 компилятор должен создаеть такое:
<псевдокод>
C(const C&)
{
B(C) — конструирование базового класса
С(С) — конструирование инстанцируемого класса
}
что он и делает. если бы не был определен конструктор B(const C&), то компилятор бы сделал дополнительную функцию для приведения типа и вызов бы был таким:
<псевдокод>
C(const C&)
{
operator B(const C&) — оператор приведения типа
B(operator B(C)) — конструирование базового класса
С(С) — конструирование инстанцируемого класса
}
но в данном случае этого не происходит, т.к. преобразованием типа компилятор занимается только если отсутствуют явно определенные функции с подходящей сигнатурой, а в твоем примере они присутствуют (конструктор B(C)). ты же требуешь от компилятора фактически того, что раз для производного класса конструктор копирования не определен, то считать не определенными и конструкторы в производных классах (т.е. автоматически генерировать и их, т.к. в этом случае B(B) ничем не лучше B(C), т.е. на твою версию B(B) надо забить, а это нонсенс)
_>>В строке 8 компилятор должен создавать правильный "The implicitly-defined copy constructor", который должен вызывать для каждого "of its subobjects" "the copy constructor", т.е. B::B(const B &). А вызываеться B(const C &), т.е. бред какой то, ч.т.д.
B>это с какой такой великой радости он это должен ?
, а то мы ходим по кругу. Преобразования типов здесь вообще ни при чем.
как это ни при чем ? а где компилятор должен взять это B? чтобы вызвать B(B) ?
c одной стороны ты требуешь побитового (memberwise) копирования (ссылаясь на стандарт), а с другой создаешь пользовательские конструкторы на разные случаи жизни. из этого следует 2 вывода:
1. ни о каком побитовом копировании речи уже не идет, т.к. мало ли что ты делаешь в конструкторе копирования (компилятор эб этом не знает).
2. определение B(C) прямо предписывает компилятору использовать конструктор B(C), если это самое С ему на вход подается. в данном контексте, если производный класс С, то использовать B(C), если любой другой, то использовать B(B). что компилятор послушно и делает
, а то мы ходим по кругу. Преобразования типов здесь вообще ни при чем.
B>как это ни при чем ? а где компилятор должен взять это B? чтобы вызвать B(B) ?
B>c одной стороны ты требуешь побитового (memberwise) копирования (ссылаясь на стандарт), а с другой создаешь пользовательские конструкторы на разные случаи жизни. из этого следует 2 вывода:
Это такой способ издеваться? Перевожу:
12.8.8.
Неявный конструктор копирования для класса X (в нашем случае C) выполняет копирование его составляющих (причем здесь побитовое копирование bitwise copy?). Порядок копирования такой же как и порядок инициализации баз (или наследуемых классов, как Вам больше нравиться) и членов (мемберов, полей и т.п.) в определенном пользователем конструкторе (см. 12.6.2). Каждый такая составляющая копируеться в соответствии с её типом:
— если это class (как в нашем случае B), то используеться конструктор копирования этого класса (т.е. B::B(const B &), а не B::B(const C&)).
— если это массив, то каждый элемент этого массива копируеться в соотвествии с его типом.
— если это скалярный тип, то вызываеться встроенный оператор присваивания.
, а то мы ходим по кругу. Преобразования типов здесь вообще ни при чем.
B>как это ни при чем ? а где компилятор должен взять это B? чтобы вызвать B(B) ?
B>c одной стороны ты требуешь побитового (memberwise) копирования (ссылаясь на стандарт), а с другой создаешь пользовательские конструкторы на разные случаи жизни. из этого следует 2 вывода:
B>1. ни о каком побитовом копировании речи уже не идет, т.к. мало ли что ты делаешь в конструкторе копирования (компилятор эб этом не знает). B>2. определение B(C) прямо предписывает компилятору использовать конструктор B(C), если это самое С ему на вход подается. в данном контексте, если производный класс С, то использовать B(C), если любой другой, то использовать B(B). что компилятор послушно и делает
Вот ведь упорный какой Не надо ничего додумывать.
В цитате английским языком было сказано, что для инициализации подобъектов используется конструктор копирования. А конструктором копирования класса X является конструктор, первый параметр которого имеет тип X&, X const&, X volatile& или X const volatile& (12.8/2). Так что конструктор B(C) не является конструктором копирования и таким образом не может быть выбран.
вот это — стандартное поведение компилятора, которое и описывает твоя выдержка из стандарта
<псевдокод>
C(const C&)
{
B(operator B(C)) — конструирование базового класса с применением неявного приведения типа
С(С) — конструирование инстанцируемого класса
}
operator B(const C&) — оператор приведения типа
так и происходит в твоем примере при отсутствии конструктора B(C). но введя его ты сам, добровольно, меняешь логику принятую по умолчанию, т.к. в дело вступает другая строка стандарта (искать не буду) о применении неявного приведения типа. и эта строка стандарта явно запрещает это приведение в слуае если определена функция с точной сигнатурой. что и происходит в твоем случае.
Здравствуйте, folk, Вы писали:
F>Вот ведь упорный какой Не надо ничего додумывать.
а ничего не додумывается
просто в данном конкретном примере вступают в противоречие 2 предписания:
1. вызов конструктора копирования
2. запрет неявного приведения типа при наличии определениия функции с точной сигнатурой.
какое из этих предписаний важнее ?
MS посчитала, что второе ...
Здравствуйте, Bork, Вы писали:
B>а ничего не додумывается B>просто в данном конкретном примере вступают в противоречие 2 предписания: B>1. вызов конструктора копирования
Спасибо за то что приняли правила неявного копирования.
B>2. запрет неявного приведения типа при наличии определениия функции с точной сигнатурой.
Теперь по поводу противоречий, можно пункт из стандарта, который противоречит 12.8.8?
Здравствуйте, sergey_shandar, Вы писали:
_>Здравствуйте, Bork, Вы писали:
B>>а ничего не додумывается B>>просто в данном конкретном примере вступают в противоречие 2 предписания: B>>1. вызов конструктора копирования _>Спасибо за то что приняли правила неявного копирования.
так я и не отказывался
B>>2. запрет неявного приведения типа при наличии определениия функции с точной сигнатурой. _>Теперь по поводу противоречий, можно пункт из стандарта, который противоречит 12.8.8?
правила неявного преобразования типов параметров при вызове функций что ли ? так поищи их сам, тем более что стандарт у тебя под рукой. ну и естественно что там не написано о том, что такой то пункт противоречит пункту 12.8.8.
но вспомни механизм работы конструктора, порядок вызова конструкторов базовых классов, порядок инициализации членов и подумай откуда берутся параметры для неявного конструктора копирования.
и ты тоже увидишь проблему в своем примере, которую MS трактует в пользу приведения типа, а разработчики GCC в пользу конструктора копирования. кто из них прав с точки зрения стандарта я сказать не могу, имею лишь свое мнение, которое склоняется в сторону MS, т.к. логически более выдержанное (опять же с моей точки зрения).
Здравствуйте, sergey_shandar, Вы писали:
_>Здравствуйте, Bork, Вы писали:
давай рассмотрим такой пример
struct C;
struct B
{
void fn(const B &) { std::cout << "class B" << std::endl; }
void fn(const C &) { std::cout << "class C" << std::endl; }
};
struct C: B {};
struct D: B {};
int _tmain(int argc, _TCHAR* argv[])
{
C c;
c.fn(c); // выводит "class C"
D d;
d.fn(d); // выводит "class B"
return 0;
}
надеюсь результат тебя не обескураживает ?
так чем же конструкторы то лучше ? с точки зрения языка такие же функции, к которым применимы те же правила приведения типов
Здравствуйте, Bork, Вы писали:
B>давай рассмотрим такой пример
B>struct C; B>struct B B>{ B> void fn(const B &) { std::cout << "class B" << std::endl; } B> void fn(const C &) { std::cout << "class C" << std::endl; } B>};
B>struct C: B {}; B>struct D: B {};
B>int _tmain(int argc, _TCHAR* argv[]) B>{ B> C c; B> c.fn(c); // выводит "class C" B> D d; B> d.fn(d); // выводит "class B" B> return 0; B>}
B>надеюсь результат тебя не обескураживает ? B>так чем же конструкторы то лучше ? с точки зрения языка такие же функции, к которым применимы те же правила приведения типов
Псевдокода, который ты написал нет в Стандарте, там просто сказано что должен генерироваться конструктор копирования, который выполняет копирование всех подобъектов. Более того, этот мой пример противоречит твоей трактовке поведение MSVC:
#include <stdio.h>
struct A;
struct B;
struct A
{
A(){}
A(const B&)
{
printf("A::A(const B&)\n");
}
};
struct B
{
B(){}
B(const A&)
{
printf("B::B(const A&)\n");
}
};
struct C : A, B
{
};
int main()
{
C c1;
C c2(c1);
}
Следуя твоей логике он должен выдеть здесь неоднозначность, а он успешно выбирает конструктор копирования.
Здравствуйте, Bork, Вы писали:
B>давай рассмотрим такой пример
скипнуто
B>надеюсь результат тебя не обескураживает ?
Нет, не обескураживает.
B>так чем же конструкторы то лучше ?
Ничем.
B>с точки зрения языка такие же функции, к которым применимы те же правила приведения типов
И? А причем здесь то как генерируеться неявный конструктор?
По моей логике и по стандарту: неявный конструктор, должен вызывать конструктор копирования для каждой части класса. Так как части у двух одинаковых классов одинаковые (по своей структуре), то приведенея типов здесь совершенно не при чем. А если следовать тому что ты хочешь сказать, тогда и к таким примерам не долго дойти:
struct A {};
struct B: A
{
A a;
B(const B &b_): A(b_), a(b_) {}
};
Никакого явного приведения типов, наслаждайся ....
Здравствуйте, dupamid, Вы писали:
D>Здравствуйте, Bork, Вы писали:
D>Псевдокода, который ты написал нет в Стандарте, там просто сказано что должен генерироваться конструктор копирования, который выполняет копирование всех подобъектов. Более того, этот мой пример противоречит твоей трактовке поведение MSVC:
это был не псевдокод, а реальная программа, которая работает как ожидается
D>
D>#include <stdio.h>
D>struct A;
D>struct B;
D>struct A
D>{
D> A(){}
D> A(const B&)
D> {
D> printf("A::A(const B&)\n");
D> }
D>};
D>struct B
D>{
D> B(){}
D> B(const A&)
D> {
D> printf("B::B(const A&)\n");
D> }
D>};
D>struct C : A, B
D>{
D>};
D>int main()
D>{
D> C c1;
D> C c2(c1);
D>}
D>
D>Следуя твоей логике он должен выдеть здесь неоднозначность, а он успешно выбирает конструктор копирования.
а здесь нет никакого противоречия, т.к. согласно объявлению класса С конструирование происходит следующим образом :
1. вызов конструктора класса А, с передачей ему объекта с1.
класс A не знает как преобразовать класс С в класс В, но знает как превратиь С в А поэтому генерируется конструктор по умолчанию A(operator А(С)), который и вызывается.
2. вызов конструктора класса B, с передачей ему объекта с1.
здесь происходит тоже самое что и в п.1
3. вызов конструктора С(С).
результат — вывода на экран не происходит.
так что пример не удачен.
хм..
но самое интересное, что конструкторы
A(const C&)
{
printf("A::A(const C&)\n");
}
и
B(const C&)
{
printf("B::B(const C&)\n");
}
тоже вызываться не будут если параллельно не будут объявлены стандартные конструкторы копирования
A(const A&) и B(const B&) ... а вот это уже косяк
Здравствуйте, sergey_shandar, Вы писали:
_>Здравствуйте, Bork, Вы писали:
B>>с точки зрения языка такие же функции, к которым применимы те же правила приведения типов _>И? А причем здесь то как генерируеться неявный конструктор?
так в твоем то примере он как раз и не генерируется, а определяется тобой
а вот если его не определить, то он как раз и будет сгенерирован и вызова B(C) не произойдет
_>По моей логике и по стандарту: неявный конструктор, должен вызывать конструктор копирования для каждой части класса. Так как части у двух одинаковых классов одинаковые (по своей структуре), то приведенея типов здесь совершенно не при чем. А если следовать тому что ты хочешь сказать, тогда и к таким примерам не долго дойти: _>
_>struct A {};
_>struct B: A
_>{
_> A a;
_> B(const B &b_): A(b_), a(b_) {}
_>};
_>
Здравствуйте, Bork, Вы писали:
B>тоже вызываться не будут если параллельно не будут объявлены стандартные конструкторы копирования B>A(const A&) и B(const B&) ... а вот это уже косяк
впрочем при размышлении так и должно быть, потому что при отсутствии конструктора копирования выбирать не из чего и он (конструктор копирования) генерируется компилятором
Здравствуйте, Bork, Вы писали:
D>>Псевдокода, который ты написал нет в Стандарте, там просто сказано что должен генерироваться конструктор копирования, который выполняет копирование всех подобъектов. Более того, этот мой пример противоречит твоей трактовке поведение MSVC:
B>это был не псевдокод, а реальная программа, которая работает как ожидается
. Если ты считаешь это настоящим кодом, то видимо спорить вообще безсмысленно...
D>>Следуя твоей логике он должен выдеть здесь неоднозначность, а он успешно выбирает конструктор копирования.
B>а здесь нет никакого противоречия, т.к. согласно объявлению класса С конструирование происходит следующим образом :
B>1. вызов конструктора класса А, с передачей ему объекта с1. B>класс A не знает как преобразовать класс С в класс В, но знает как превратиь С в А поэтому генерируется конструктор по умолчанию A(operator А(С)), который и вызывается.
Почему это класс А не знает как преобразовать С в B? Отлично знает это точно такое же преобразование производный -> базовый как C -> A.
B>2. вызов конструктора класса B, с передачей ему объекта с1. B>здесь происходит тоже самое что и в п.1
B>3. вызов конструктора С(С).
Какого конструктора С? Это и так конструктор С, один конструктор вызывает другой конструктор того же класса???
B>хм.. B>но самое интересное, что конструкторы B>A(const C&) B>{ B> printf("A::A(const C&)\n"); B>} B>и B>B(const C&) B>{ B> printf("B::B(const C&)\n"); B>} B>тоже вызываться не будут если параллельно не будут объявлены стандартные конструкторы копирования B>A(const A&) и B(const B&) ... а вот это уже косяк
Это как раз правильно! В сгенерированной компилятором конструкторе копирования, могут вызываться только конструкторы копирования.