"Возможно, я простой провинциальный адвокат и ничего не смыслю в суперзлодеях" ("Братья Вентура")
Доброго времени суток читающим!
Возник вот такой вопрос: я создаю временный экземпляр класса и передаю его в качестве аргумента по ссылке. Вот примитивный тестовы пример:
#include <iostream>
using std::cout;
using std::endl;
class Foo {
public:
int m_data;
Foo(const int& data):
m_data(data) {};
private:
Foo(const Foo& foo);
};
void proc_foo(const Foo& foo) {
cout << "Data: " << foo.m_data << endl;
};
int main(int argc, char* argv[]) {
proc_foo(Foo(5));
return 0;
};
Компилятор GCC 4.1.2 ругается:
main.cpp: In function ‘int main(int, char**)’:
main.cpp:13: error: ‘Foo::Foo(const Foo&)’ is private
main.cpp:21: error: within this context
Насколько я понял, он не хочет передавать по ссылке созданную при вызове конструктора временную переменную, а хочет передать ее копию. Это по стандарту так, или косяки самого компилятора? Или я чего-то не учел?
Заранее спасибо.
Люди! Люди, смотрите, я сошел с ума! Люди! Возлюбите друг друга! (вы чувствуете, какой бред?)
Здравствуйте, slava_phirsov, Вы писали:
_>Компилятор GCC 4.1.2 ругается:
_>
_>main.cpp: In function ‘int main(int, char**)’:
_>main.cpp:13: error: ‘Foo::Foo(const Foo&)’ is private
_>main.cpp:21: error: within this context
_>Насколько я понял, он не хочет передавать по ссылке созданную при вызове конструктора временную переменную, а хочет передать ее копию. Это по стандарту так, или косяки самого компилятора? Или я чего-то не учел? _>Заранее спасибо.
Это copy-initialization(8.5 п.12), что эквивалентно такой записи:
const Foo& foo = Foo(5);
В текущей версии стандарта такое требует конструктора копирования, даже если копирования не произойдёт
(8.5.3 п.5):
The constructor that would be used to make the copy shall be callable whether or not the copy is
actually done. [Example:
struct A { };
struct B : public A { } b;
extern B f();
const A& rca = f(); // Either bound to the A sub-object of the B rvalue,
// or the entire B object is copied and the reference
// is bound to the A sub-object of the copy
_>Насколько я понял, он не хочет передавать по ссылке созданную при вызове конструктора временную переменную, а хочет передать ее копию. Это по стандарту так, или косяки самого компилятора? Или я чего-то не учел?
Проблема не в передаче аргумента в функцию, а в создании промежуточного объекта. Для создания промежуточного объекта действительно нужен конструктор копирования.
Такая проблема решается следующим образом:
const Foo f(5);
proc_foo(f);
В этом случае конструктор копирования не нужен, так как мы явно создаем промежуточный объект через публичный конструктор.
На самом деле такая запись даже более предпочтительна, потому что более читабельна и более безопасна.
Здравствуйте, Николай Ивченков, Вы писали:
НИ>Hooter:
_H>>На самом деле такая запись даже более предпочтительна, потому что более читабельна и более безопасна.
НИ>Это вряд ли. Да и не везде такое применишь (например, в списке инициализации конструктора и в аргументе по умолчанию негде объявлять переменные).
Значит лучше что бы копи-конструктор создавал объект? С глаз долой и из сердца вон!
Только если он явно не определён (что очень часто встречается), то во-первых, скорее всего объект создаётся где придётся, i.e. на стеке. Соответственно, если число таких неявных созданий растёт (ведь в вашем примере с инициализацией в списке конструктора придётся незабыть и про удаление в деструкторе), имеем проблемы перерполнения стека. А во-вторых,что бы найти такой трик в проге — надо изрядно попотеть, так как понять такую "неявность" сначало трудно — подСознание обманчиво подсказывет, что надо искать место, где объект создаётся...и начинается долгий путь к великому замыслу уволевшегося коллеги...
Думаю Hooter прав и насчёт читабельности.
Кстати, кроме приведённых Вами примеров, ситуации, где такое неявное создание необходимо, в голову не приходят. И тем более, ситуации, где по другому нельзя. Может с архитектурой что-то не так, раз понадобились такие пути? Это IMHO, поэтому, просвятите, плз.
Здравствуйте, _Hooter, Вы писали:
_H>Проблема не в передаче аргумента в функцию, а в создании промежуточного объекта. Для создания промежуточного объекта действительно нужен конструктор копирования.
В этом случае объект создаётся всего один (копирование не выполняется), ктр. живет до выхода из функции. Такое поведение, насколько я понимаю, соответствует ожиданиям автора исходного сообщения.
Другое дело, что компилятор (в соответствии с требованием стандарта) требует открытого конструктора копирования. Что, на мой взгляд, не совсем логично. В c++0x это требование убрали. Те компиляторы (как уже говорили выше), ктр. поддерживают фичи нового стандарта, прекрасно компилируют такой код
Здравствуйте, igna, Вы писали:
I>Здравствуйте, _Hooter, Вы писали:
_H>>На самом деле такая запись даже более предпочтительна, потому что более читабельна и более безопасна.
I>Чем же она более безопасна?
В специфических контекстах такое использование (отдельный объект) м. б. безопасней с точки зрения потенциальных утечек, см. например boost::ptr_multimap_adapter:
// При использовании 'const key_type& k' в качестве первого параметра
// гарантию отсутствия утечек (strong exception guarantee) можно
// обеспечить только с использованием отдельного объекта при вызове.
iterator insert( key_type& k, T* x );
template< class U >
iterator insert( const key_type&, std::auto_ptr<U> x1 );
Обе функции передают владение указателем контейнеру. Первый вариант фактически принуждает к созданию отдельного объекта 'key_type', иначе (с использование 'const key_type& k') при возбуждении исключения в конструкторе 'key_type' при создании временного объекта, указатель 'x' может утечь. Потенциальная утечка связана с тем, что порядок вычисления арументов неопределен и сначала может произойти вычисление второго аргумента (например 'new T(args)'), а при вычислении второго может возникнуть исключение. Для исключения этой ситуации квалификатор 'const' отсутствует в первой функции, и поэтому вычисление первого аргумента никогда не заканчивается исключением.
Artom:
A>Значит лучше что бы копи-конструктор создавал объект?
Как много реализаций создают-таки копию при инициализации ссылки? =) В будущем стандарте это будет вообще под запретом. Ну, да, есть маленький шанс столкнуться с экзотическим компилятором и получить overhead. Но при чём тут читаемость и безопасность? Читаемость-то как раз лучше с temporary objects, т.к. для читателя их время жизни и использование максимально локализованы. Определение же переменной зрительно отделено от вызова функции, и переменная потенциально может быть использована в нескольких местах.
И, кстати, если в некоей f передаваемый аргументом объект дальше где-то сохраняется, то в дальнейшем к f(X const &) можно будет добавить f(X &&), где копирование может быть заменено перемещением. К переменной же f(X &&) применяться не будет.
A>Только если он явно не определён (что очень часто встречается), то во-первых, скорее всего объект создаётся где придётся, i.e. на стеке. Соответственно, если число таких неявных созданий растёт (ведь в вашем примере с инициализацией в списке конструктора придётся незабыть и про удаление в деструкторе), имеем проблемы перерполнения стека.
Это сколько ж надо насоздавать объектов, чтобы переполнение стека получить?
A>Кстати, кроме приведённых Вами примеров, ситуации, где такое неявное создание необходимо, в голову не приходят.
while (f(T(x)))
{
.... // здесь x меняется
}
A>Может с архитектурой что-то не так, раз понадобились такие пути?
Ну, конечно, как чуть что, так сразу с архитектурой что-то не так. И откуда только такие умные архитекторы берутся? В некоторых (если не в большинстве) случаях(-ев) две архитектуры вообще невозможно сравнить на предмет "лучше"/"хуже" в целом. Подобными сравнениями обычно занимаются пиарщики или флеймеры-холиварщики, я же в этом предпочитаю не участвовать (за редкими исключениями).
Здравствуйте, Николай Ивченков, Вы писали:
НИ>Artom:
A>>Значит лучше что бы копи-конструктор создавал объект?
НИ>Как много реализаций создают-таки копию при инициализации ссылки? =)В будущем стандарте это будет вообще под запретом.
Что именно имеется ввиду? У меня, видимо, отличные крепкие ассоциации со словом "Реализация"...
НИ>Ну, да, есть маленький шанс столкнуться с экзотическим компилятором и получить overhead. Но при чём тут читаемость и безопасность? Читаемость-то как раз лучше с temporary objects, т.к. для читателя их время жизни и использование максимально локализованы.
Согл., для тех, кто предупреждён — да!
НИ>Определение же переменной зрительно отделено от вызова функции, и переменная потенциально может быть использована в нескольких местах.
Конечно тут я соглашусь, НО мы же говорили не об определении переменных и не о простом их использовании! Речь шла о создании временных объектов, не так ли? Или мы говорим о разных вещах? Тогда — пардон.
НИ>И, кстати, если в некоей f передаваемый аргументом объект дальше где-то сохраняется, то в дальнейшем к f(X const &) можно будет добавить f(X &&), где копирование может быть заменено перемещением. К переменной же f(X &&) применяться не будет.
А по-моему, это только усугубит проблему поиска места истинного создания...
A>>Только если он явно не определён (что очень часто встречается), то во-первых, скорее всего объект создаётся где придётся, i.e. на стеке. Соответственно, если число таких неявных созданий растёт (ведь в вашем примере с инициализацией в списке конструктора придётся незабыть и про удаление в деструкторе), имеем проблемы перерполнения стека. НИ>Это сколько ж надо насоздавать объектов, чтобы переполнение стека получить?
Случай из практики...Например графика. Допустим, объект в данном случае был Mesh или Poligone! Воображеие рисует уже?
A>>Кстати, кроме приведённых Вами примеров, ситуации, где такое неявное создание необходимо, в голову не приходят. НИ>
while (f(T(x)))
НИ>{
НИ> .... // здесь x меняется
НИ>}
Да я же не имею в виду код создания! Это уже автор топика показал — зачем же дублировать. Я имею в виде именно реальную архитектуру приложения, где такое неявное создание просто незаменимо! И не для понта спрашиваю, а потому что лично такого не встречалось. Обычно было так, что объекты со сложными конструкторами возникали на старейших фазах проекта, когда надо было какие-то проблемы ранней архитектуры залатать. Так же относительно следующего:
A>>Может с архитектурой что-то не так, раз понадобились такие пути? НИ>Ну, конечно, как чуть что, так сразу с архитектурой что-то не так. И откуда только такие умные архитекторы берутся? В некоторых (если не в большинстве) случаях(-ев) две архитектуры вообще невозможно сравнить на предмет "лучше"/"хуже" в целом. Подобными сравнениями обычно занимаются пиарщики или флеймеры-холиварщики, я же в этом предпочитаю не участвовать (за редкими исключениями).
Жаль что до сих пор так считаете.
Artom:
НИ>>Как много реализаций создают-таки копию при инициализации ссылки? =)В будущем стандарте это будет вообще под запретом. A>Что именно имеется ввиду?
Конкретные компиляторы.
НИ>>Ну, да, есть маленький шанс столкнуться с экзотическим компилятором и получить overhead. Но при чём тут читаемость и безопасность? Читаемость-то как раз лучше с temporary objects, т.к. для читателя их время жизни и использование максимально локализованы. A>Согл., для тех, кто предупреждён — да!
О чём предупреждён?
НИ>>Определение же переменной зрительно отделено от вызова функции, и переменная потенциально может быть использована в нескольких местах. A>Конечно тут я соглашусь, НО мы же говорили не об определении переменных и не о простом их использовании! Речь шла о создании временных объектов, не так ли?
В терминологии стандарта
const Foo f(5);
это именно определение переменной, где f — переменная, а не временный объект.
НИ>>И, кстати, если в некоей f передаваемый аргументом объект дальше где-то сохраняется, то в дальнейшем к f(X const &) можно будет добавить f(X &&), где копирование может быть заменено перемещением. К переменной же f(X &&) применяться не будет. A>А по-моему, это только усугубит проблему поиска места истинного создания...
Какие-то странные у тебя проблемы. Здесь речь, в общем-то, идёт об обычной оптимизации, достигаемой за счёт семантики перемещения.
НИ>>Это сколько ж надо насоздавать объектов, чтобы переполнение стека получить? A> Случай из практики...Например графика. Допустим, объект в данном случае был Mesh или Poligone! Воображеие рисует уже?
И что — все данные тяжёлого объекта ты хранишь в автоматически распределяемой памяти? Ну, тогда ты ССЗБ.
A>Я имею в виде именно реальную архитектуру приложения, где такое неявное создание просто незаменимо!
А кому нужны именно _незаменимые_ решения? Если решение поставленной задачи можно заменить на другое, то что тогда?
Здравствуйте, Николай Ивченков, Вы писали:
НИ>Artom: НИ>>>Определение же переменной зрительно отделено от вызова функции, и переменная потенциально может быть использована в нескольких местах. A>>Конечно тут я соглашусь, НО мы же говорили не об определении переменных и не о простом их использовании! Речь шла о создании временных объектов, не так ли?
НИ>В терминологии стандарта НИ>
const Foo f(5);
НИ>это именно определение переменной, где f — переменная, а не временный объект.
смотря что вы называете определене переменной: initialization или assignment. Если ещё более конкретнее, то в терм. станд. это инициализация переменной. но в данм примере это может быть и присваивание. Давайте определимся с терминами, плз.
НИ>>>И, кстати, если в некоей f передаваемый аргументом объект дальше где-то сохраняется, то в дальнейшем к f(X const &) можно будет добавить f(X &&), где копирование может быть заменено перемещением. К переменной же f(X &&) применяться не будет. A>>А по-моему, это только усугубит проблему поиска места истинного создания... НИ>Какие-то странные у тебя проблемы. Здесь речь, в общем-то, идёт об обычной оптимизации, достигаемой за счёт семантики перемещения.
Что такое семантика перемещения? Плз! Представляется некий теоретический монстр, сорри!
НИ>>>Это сколько ж надо насоздавать объектов, чтобы переполнение стека получить? A>> Случай из практики...Например графика. Допустим, объект в данном случае был Mesh или Poligone! Воображеие рисует уже? НИ>И что — все данные тяжёлого объекта ты хранишь в автоматически распределяемой памяти? Ну, тогда ты ССЗБ.
Что по вашему есть авто распред. памяти? Впервые слышу термин. Плз., поясните! Может вы имеете ввиду диамически-распред. память. Тогда да — я распределяю память динамически для как можно большего количества объектов. А что, по вашему я должен ложить все на стек?
Что такое ССЗБ? Надеюсь это не оскорбительное!
A>>Я имею в виде именно реальную архитектуру приложения, где такое неявное создание просто незаменимо! НИ>А кому нужны именно _незаменимые_ решения? Если решение поставленной задачи можно заменить на другое, то что тогда?
Но зачем тогда усложнять?
Artom:
A>смотря что вы называете определене переменной: initialization или assignment.
Definition, initialization и assignment — это три разных термина. Определением я называю definition (что, вроде как, общепринято).
A>Что такое семантика перемещения? A>Что такое ССЗБ?
Здесь я имел в виду память, принадлежащую непосредственно объекту с автоматическим временем жизни.
A>я распределяю память динамически для как можно большего количества объектов.
Тогда я не понимаю, откуда, по-твоему, должно возникать переполнение стека.
A>>>Я имею в виде именно реальную архитектуру приложения, где такое неявное создание просто незаменимо! НИ>>А кому нужны именно _незаменимые_ решения? Если решение поставленной задачи можно заменить на другое, то что тогда? A>Но зачем тогда усложнять?