Строго говоря, если класс DataMapperImpl не является полностью определенным в месте определения члена _pimpl, выполнение или компиляция программы, содержащей подобный фрагмент, согласно п. 17.4.3.6/2 текущего стандарта, приводит к undefined behavior:
17.4.3.6 Other functions
2 In particular, the effects are undefined in the following cases:
— if an incomplete type (3.9) is used as a template argument when instantiating a template component.
Т.к. определение нестатической переменной-члена требует полностью определенного типа (9.2/8), точкой инстанциирования шаблона std::auto_ptr<> в данном случае является определение члена _pimpl в определении класса DataMapper (14.7.1).
Примером проявления упомянутого неопределенного поведения может служить ошибка компиляции, если определение шаблона std::auto_ptr<> содержит sizeof(T). Конечно, кто-нибудь может — весьма справедливо — поинтересоваться: зачем это в определении std::auto_ptr<> может понадобиться sizeof(T)? Естественно, для std::auto_ptr<> разумного ответа, скорее всего, не последует. Тем не менее, все это соображения о деталях реализации стандартной библиотеки, что, очевидно, является не более чем спекуляцией. Единственным — законным — руководством оценки правильности использования стандартной библиотеки является стандарт. В том виде, как он написан сейчас, стандарт однозначно трактует подобную программу как приводящую к неопределенному поведению. При написании стандарта, члены рабочей группы решили воздержаться от классификации шаблонов стандартной библиотеки на те, которые можно инстанциировать неполными типами, и те, которые нельзя, ограничившись общей "запретительной" формулировкой (более подробно см., например, статью Containers of Incomplete Types, автором которой является Matt Austern, председатель "библиотечной" рабочей группы комитета стандартизации).
С практической точки зрения часто делают допущение, что неопределенность поведения, упомянутая в п. 17.4.3.6/2, применительно к std::auto_ptr<>, относится к неопределенности поведения при инстанциировании деструктора std::auto_ptr<>, который в том или ином виде должен содержать выражение вида: delete p. Подобные размышления можно встретить и в небезызвестной статье Герба Саттера. Однако следует помнить, что в основании всех подобных рассуждений лежат предположения о реализации стандартной библиотеки, ни в коей мере не подкрепленные стандартом.
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, ssm, Вы писали:
ssm>Здравствуйте, grs, Вы писали:
grs>>А за "варианты покрасивие" нормальные руководители проекта отрывают руки.
ssm>Вопервых "нормальные руководители" вообще не должны лазить по исходникам : это — не их парафия, их задача — правильно сделать постановку задачи и, может быть в некоторых случаях, предоставить программисту открытый интерфейс реализуемого им класса.
grs>> И правильно делают...
ssm>а во вторых, если уж "нормальные руководители" начали лазить по коду, то незнание патерна(или как это правильно называеться) pimpl вообще ставит под сомнение их уровень квалификации
Ну не надо доводить мои слова до абсурда. Шаблоны (или паттерны) тут вообще не причем, тут речь идет об элементарных правилах программирования на С++. Особенно, если работаешь в команде.
А по поводу руководителя, а он по исходникам и не лазает. Объясняю алгоритм. Получаешь ты такой шедевр типа someclass.h. Подходишь к автору и говоришь, что у меня мол нечего не компилится не фига. В ответ на предложение включить такие-то и такие-то заголовки, вежливо объясняешь ему, что это его проблема. Если не понимает — идешь к руководителю, а он приступает к ампутации конечностей. Все просто!
Здравствуйте, grs, Вы писали:
grs>Ну не надо доводить мои слова до абсурда. Шаблоны (или паттерны) тут вообще не причем, тут речь идет об элементарных правилах программирования на С++. Особенно, если работаешь в команде. grs>А по поводу руководителя, а он по исходникам и не лазает. Объясняю алгоритм. Получаешь ты такой шедевр типа someclass.h. Подходишь к автору и говоришь, что у меня мол нечего не компилится не фига.
Мы же ведь говорили о "вариантах покрасивие", а не о приведенном автором коде
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>С практической точки зрения часто делают допущение, что неопределенность поведения, упомянутая в п. 17.4.3.6/2, применительно к std::auto_ptr<>, относится к неопределенности поведения при инстанциировании деструктора std::auto_ptr<>, который в том или ином виде должен содержать выражение вида: delete p. Подобные размышления можно встретить и в небезызвестной статье Герба Саттера. Однако следует помнить, что в основании всех подобных рассуждений лежат предположения о реализации стандартной библиотеки, ни в коей мере не подкрепленные стандартом.
Я, безусловно, снимаю свою несуществующую шляпу перед Павлом.
Но в своей статье Саттер говорит, что чтобы избежать пресловутого undefined behaviour необходимо определить конструктор, копирующий конструктор, оператор присваивания и деструктор.
В форумах, подобных comp.lang.c++.moderated, Саттер неоднократно отвечал на подобные вопросы.
Думаю, что он достатчно авторитетен и осведомлен (Secretary, ISO WG21/ANSI J16 (C++) standards committee) обо всех тонкостях и подводных камнях использования pimpl и auto_ptr.
Здравствуйте, MaximE, Вы писали:
ПК>>часто делают допущение, что неопределенность поведения, упомянутая в п. 17.4.3.6/2, применительно к std::auto_ptr<>, относится к <...>
ME>в своей статье Саттер говорит, что чтобы избежать пресловутого undefined behaviour необходимо определить конструктор, копирующий конструктор, оператор присваивания и деструктор.
Что свидетельствует о наличии некоторых — возможно, вполне обоснованных с практической точки зрения — предположений о реализации std::auto_ptr<>. Для того, чтобы оценить границы применимости подобных рассуждений, просто скопируй куда-нибудь реализацию std::auto_ptr<>, поставляемую с твоим компилятором, и добавь в начало определения нового (назовем его my_auto_ptr<>) шаблона строку:
enum { __element_size = sizeof(_Ty) };
заменив _Ty на имя параметра шаблона, если оно отличается. Полученный вариант, будучи поставляем разработчиком компилятора, в той же мере соответствует стандарту, что и первоначальная версия. Теперь попробуй инстанциировать полученный шаблон my_auto_ptr<> неполным типом. Неизбежная ошибка компиляции — проявление неопределенного поведения, которое не выйдет обойти никакими конструкторами или деструкторами.
ME>В форумах, подобных comp.lang.c++.moderated, Саттер неоднократно отвечал на подобные вопросы.
В этих "ответах" Саттер, подразумевая истинность своих предположений о реализации std::auto_ptr<>, делает какие-то выводы, упоминая главу 12 стандарта и т.п. Ни одного прямого ответа на упоминание п. 17.4.3.6/2, насколько мне известно, он не давал.
ME>Думаю, что он достатчно авторитетен и осведомлен (Secretary, ISO WG21/ANSI J16 (C++) standards committee)
Опуская неизбежные комментарии о "громкой" должности секретаря комитета, замечу, что обосновывать правоту суждений авторитетом автора не вполне корректно.
ME>обо всех тонкостях и подводных камнях использования pimpl и auto_ptr.
Однако специально для любителей авторитетов я привел ссылку на статью Мэта Остерна, куда уж как более авторитетного в данных вопросах, нежели Саттер. Статья включает, помимо соответствующих формулировок, и обоснование решений, принятых комитетом в этом отношении.
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Согласен со всем, но одно маленькое "но":
ПК>заменив _Ty на имя параметра шаблона, если оно отличается. Полученный вариант, будучи поставляем разработчиком компилятора, в той же мере соответствует стандарту, что и первоначальная версия. Теперь попробуй инстанциировать полученный шаблон my_auto_ptr<> неполным типом. Неизбежная ошибка компиляции — проявление неопределенного поведения, которое не выйдет обойти никакими конструкторами или деструкторами.
Т.е. неопределенного поведения не возникает, так как невозможно скомпилировать. Это говорит только о том, что инстанцирование происходит в объявлении класса.
Здравствуйте, MaximE, Вы писали:
ПК>>Неизбежная ошибка компиляции — проявление неопределенного поведения
ME>неопределенного поведения не возникает, так как невозможно скомпилировать
Невозможность скомпилировать — один из многих вариантов проявления неопределенного поведения :-)
1.3.12 undefined behavior
behavior, such as might arise upon use of an erroneous program construct or erroneous data, for which this International Standard imposes no requirements. <...> [Note: permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message).
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Здравствуйте, ssm, Вы писали:
ssm>Здравствуйте, grs, Вы писали:
ssm>Мы же ведь говорили о "вариантах покрасивие", а не о приведенном автором коде
Ага, читал. Только все эти варианты, паттерн _pimpl и т.д. — это частный случай, кстати, между нами, не так уж часто применяеемый. А ты представь, что этот преслоутый SomeClass, ну скажем диалоговое окошко, которое ты должен вызвать из своего диалогового окошка. И чего, каждый раз, будешь паттерн лепить из-за private vector<int>? Даже не смешно!
Просто я отчечал на первое сообщение в ветке, и кстати, даже не на него, а на безумное, на мой взгляд, предложение Alexey Shirshov'а о включении заголовков, которые ДОЛЖНЫ быть в someclass.h в stdafx.h, за что, повторюсь, я считаю нужно просто руки отрывать. Чтобы другим неповадно было. Просто в одном проекте, где я участвую где-то 700 исходников, а в другом — 300, я как представил картинку полной переборки при подходе Cray'я и Alexey Shirshov'а... А вы говорите паттерны, паттерны. Прежде чем заниматься высшей математикой, неплохо бы выучить таблицу умножения!
Здравствуйте, grs, Вы писали:
grs>Ага, читал. Только все эти варианты, паттерн _pimpl и т.д. — это частный случай, кстати, между нами, не так уж часто применяеемый. А ты представь, что этот преслоутый SomeClass, ну скажем диалоговое окошко, которое ты должен вызвать из своего диалогового окошка. И чего, каждый раз, будешь паттерн лепить из-за private vector<int>? Даже не смешно!
При чем тут vector<int>?
grs>Просто я отчечал на первое сообщение в ветке, и кстати, даже не на него, а на безумное, на мой взгляд, предложение Alexey Shirshov'а о включении заголовков, которые ДОЛЖНЫ быть в someclass.h в stdafx.h, за что, повторюсь, я считаю нужно просто руки отрывать.
С этим полностью согласен
grs>Чтобы другим неповадно было. Просто в одном проекте, где я участвую где-то 700 исходников, а в другом — 300, я как представил картинку полной переборки при подходе Cray'я и Alexey Shirshov'а...
Ты лучше себе преставь другую картинку: возьмем для примера windows.h, содержащем "милион" всяких определений и прочего кода. Допустим 300 файлов твоего проэкта инклудят windows.h. Что повлечет за собой изменение одного определения в windows.h ? Правильный ответ,- перекомпиляцию всех модулей его использующих. В случае же с инкапсулированной, посредством pimpl реализацией, перекомпилировать прийдеться только модули реализации. Тем более : инкапсуляция — сила, или ты и с этим не согласен? Вот тебе другой пример:конструирование необязательных мемберов объекта, или ты предлагаешь использовать дедовский двухпроходный метод ? Да и вообще зачем клиенту знать о внутренней реализации твоего класса? Его дело — использовать то, что ты ему предоставишь в открытом интерфейсе. Зачем тратить его время на компиляцию того, что можно скрыть? Скажешь мне пофиг, у моего компилера реализованы precompiled headers? Ну дык подумай о других
grs>А вы говорите паттерны, паттерны. Прежде чем заниматься высшей математикой, неплохо бы выучить таблицу умножения! Ну дык никто тебе этого незапрещает, учи на здоровье
Здравствуйте, grs, Вы писали:
> Просто в одном проекте, где я участвую где-то 700 исходников, а в другом — 300, я как представил картинку полной переборки при подходе Cray'я и Alexey Shirshov'а...
А теперь представь такую ситуацию. Есть некий проект, который условно разделен на 2 части: алгоритмическую и интерфейсную. И эти части в каких-то местах между собой взаимодействуют. Интерфейсная часть может быть написана для Windows в оконном режиме, Windows в режиме командной строки, вообще для другой операционки.
Алгоритмической части проекта (ей кроме C++ с STL ничего больше не нужно) вообще наплевать на реализацию интерфейсной части. А ты предлагаешь включить туда кучу OS-зависимых заголовочных файлов, да еще, видимо, наставить #ifdef в зависимости от варианта интерфейсной части.
Задавая вопрос, я хотел услышать варианты как обойтись без накладных расходов по вызову кучи заголовочных файлов, имеющих к алгоритмической части весьма косвнное отношение.
Про фабрики классов и pimpl-ы я знаю и этот вариант мне, в принципе, подходит. Думал, может есть еще какие варианты...
Тем не менее, дискуссия ssm-MaximE-Павел_Кузнецов была для меня довольно полезной. Узнал про "подводные камни". Спасибо.
Про то, можно ли использовать std::auto_ptr или нет тут много говорилось. А я вот хотел бы спросить- а нужно ли оно здесь? Разве нельзя создавать DataMapperImpl в конструкторе и в деструкторе потом уничтожать?
Здравствуйте, Сray, Вы писали:
С>Про то, можно ли использовать std::auto_ptr или нет тут много говорилось. А я вот хотел бы спросить- а нужно ли оно здесь? Разве нельзя создавать DataMapperImpl в конструкторе и в деструкторе потом уничтожать?
тоже небудет вызван, отсюда имеем утечку памяти. можно конечно завернуть "new DataMapperImpl()" в блок try-except, но зачем? ведь управляющие объекты были созданы специально для этих целей
Здравствуйте, ssm, Вы писали:
ssm>Здравствуйте, Сray, Вы писали:
ssm>если f() кинет exception, то объект класса DataMapper создан не будет, следовательно деструктор, в котором будет нечто
[skipped]
ssm>тоже небудет вызван, отсюда имеем утечку памяти. можно конечно завернуть "new DataMapperImpl()" в блок try-except, но зачем? ведь управляющие объекты были созданы специально для этих целей
Если конструктор кинет exception, то деструктор вызван не будет.
Здравствуйте, Павел Кузнецов, Вы писали:
ПК>Однако специально для любителей авторитетов я привел ссылку на статью Мэта Остерна, куда уж как более авторитетного в данных вопросах, нежели Саттер. Статья включает, помимо соответствующих формулировок, и обоснование решений, принятых комитетом в этом отношении.
Однако ты дал крайне провакационную ссылку
Мэт говорит, что они решили просто взять и запретить к чертям использование неполных типов для шаблонов в общем, и для STL в частности. Но далее он допускает, что не всем шаблонам обязательно нужен полный тип, и делает предположение, что в следущей версии стандарта будет указано, какие шаблоны STL допускается использовать с неполными типами.
Вывод: для собственных шаблонов мы всегда можем сказать, ведет ли их использование с неполными типами к undefined behaviour.
Что касается auto_ptr и pimpl, глубокое IMHO, семантика auto_ptr настолько ясна, что было бы удивительно, чтобы полный тип ему требовался кроме как для delete. Думаю, на этом же и основаны и известные нам рассуждения Саттера.
Здравствуйте, Сray, Вы писали:
ssm>>тоже небудет вызван, отсюда имеем утечку памяти. можно конечно завернуть "new DataMapperImpl()" в блок try-except, но зачем? ведь управляющие объекты были созданы специально для этих целей
С>Если конструктор кинет exception, то деструктор вызван не будет.
если ты о деструкторе DataMapper, то я так и написал, а если ты о деструкторе auto_ptr<> то ты ошибаешься, т.к. в процессе раскрутки стека будут вызваны деструкторы уже созданных подобъектов(базовых классов, агрегированных данным)
Здравствуйте, MaximE, Вы писали:
ME>Однако ты дал крайне провакационную ссылку :))
:-)
ME>Мэт говорит, что они решили просто взять и запретить к чертям использование неполных типов для шаблонов в общем, и для STL в частности.
"В общем" — значит для шаблонов стандартной библиотеки. К шаблонам, определяемым пользователем это отношения не имеет.
ME>делает предположение, что в следущей версии стандарта будет указано, какие шаблоны STL допускается использовать с неполными типами.
Эта тема периодически всплывает то в clc++m, то в csc++. Естественно, текущее состояние дел воспринимается большинством заинтересованных лиц слишком ограничивающим. Например, переносимая реализация шаблона контейнера, представляющего собой граф, из-за этого драконовского правила изрядно усложняется.
ME>Вывод: для собственных шаблонов мы всегда можем сказать, ведет ли их использование с неполными типами к undefined behaviour.
Безусловно.
ME>Что касается auto_ptr и pimpl, глубокое IMHO, семантика auto_ptr настолько ясна, что было бы удивительно, чтобы полный тип ему требовался кроме как для delete.
С этим я тоже полностью согласен. Именно поэтому разделил свое первоначальное сообщение на две части: "теоретическую" и "практическую". Тем не менее, факт остается фактом: использование std::auto_ptr<> для реализации PIMPL — полностью на свой страх и риск.
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Если конструктор кинет exception, то деструктор вызван не будет.
Единственное что нужно сделать ~DataMapper() — это delete _pimpl.
Он этого не сделает. Но это нормально- т.к. объект не был создан.
Поэтому я и не понимаю зачем здесь auto_ptr.
Вот если бы в конструкторе по new создавалось несколько объектов- тогда понятно. Но это не наш случай. У нас на один DataMapper приходится один DataMapperImpl
Здравствуйте, Сray, Вы писали:
С>Не дописал, sorry...
С>Если конструктор кинет exception, то деструктор вызван не будет. С>Единственное что нужно сделать ~DataMapper() — это delete _pimpl. С>Он этого не сделает. Но это нормально- т.к. объект не был создан.
Но _pimpl то уже создан!!!
С>Поэтому я и не понимаю зачем здесь auto_ptr.
Еще раз: для устранения возможной утечки памяти при генерации исключения конструктором DataMapper при уже созданом _pimpl
С>Вот если бы в конструкторе по new создавалось несколько объектов- тогда понятно. Но это не наш случай. У нас на один DataMapper приходится один DataMapperImpl
#include <iostream>
struct A{
char dummy[1000];
static int count;
A(){ count ++;}
~A(){ count --;}
};
int A::count = 0;
class B{
public:
B() : a(new A()){
throw"!!!";
}
~B() {delete a;}
private:
A * a;
};
int main(){
try{
B b;
}
catch(...)
{
std::cout << "Утечка памяти >= " << A::count * sizeof(A) << std::endl;
}
}