Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > PIMPL+auto_ptr на VS2010


Автор: azesmcar 22.1.2011, 22:44
Добрый вечер,

Есть такая проблема. Реализую PIMPL с использованием умных указателей.
Код:
x.h
Код

#ifndef x_h_included
#define x_h_included

#include <memory>

class XImpl;
class X
{
public:
    X();
    ~X();
    std::auto_ptr<XImpl> impl;
};

#endif // x_h_included

x.cpp
Код

#include "ximpl.h"
#include "x.h"

X::X()
{
}

X::~X()
{

}

ximpl.h
Код

#ifndef ximpl_h_included
#define ximpl_h_included

class XImpl
{
public:
    XImpl() {};
    ~XImpl() {};
};

#endif // ximpl_h_included

все нормально, все работает как и положено, но как только я реализую конструктор класса X в заголовочном файле (x.h) VS2010 начинает выдавать warning, что std::auto_ptr мол деструктора не видит (хотя он есть). gcc молчит, все в порядке..опять в микрософт что-то намудрили или так и положено? С решением все просто - отказаться от использования std::auto_ptr и удалять в деструкторе самому, но зачем?

Автор: alexvs11 22.1.2011, 22:48
azesmcar, auto_ptr не умный, а очень даже глупый указатель
разве shared_ptr не включили в новый стандарт?

Автор: azesmcar 22.1.2011, 22:49
Цитата(alexvs11 @  22.1.2011,  22:48 Найти цитируемый пост)
azesmcar, auto_ptr не умный, а очень даже глупый указатель

с каких это пор?

Цитата(alexvs11 @  22.1.2011,  22:48 Найти цитируемый пост)
разве shared_ptr не включили в новый стандарт? 

в тот, который еще не вышел?

Автор: alexvs11 22.1.2011, 23:02
на rsdn'e четко рассказано в чем соль
http://www.rsdn.ru/article/cpp/smartptr.xml#EMD
в немногих случаях, когда я видел применение auto_ptr это было связано с глюками, которых не было бы без его применения, а в vs для wince я помню он вообще не был полностью реализован  smile 
а shared_ptr уже в std::tr1, так что скоро будет в стандарте

Автор: mes 22.1.2011, 23:02
auto_ptr должен видеть деструктор того класса..

Автор: azesmcar 22.1.2011, 23:04
Цитата(mes @  22.1.2011,  23:02 Найти цитируемый пост)
auto_ptr должен видеть деструктор того класса.. 

Я знаю, деструктор есть. На работоспособность программы влияет не наличие деструктора, а реализация конструктора в заголовочном файле. Каким образом это связано? auto_ptr должен удаляться в деструкторе класса X, деструктор реализован в cpp файле (x.cpp), где реализация XImpl уже видна.

Цитата(alexvs11 @  22.1.2011,  23:02 Найти цитируемый пост)
на rsdn'e четко рассказано в чем соль

соль чего? auto_ptr? я с ним знаком и он меня более чем устраивает для текущей задачи.

Цитата(alexvs11 @  22.1.2011,  23:02 Найти цитируемый пост)
а shared_ptr уже в std::tr1, так что скоро будет в стандарте

скоро, не скоро..не все компиляторы его на сегодняшний день поддерживают. И кстати shared_ptr не замена auto_ptr, они для разных целей служат.

Автор: mes 22.1.2011, 23:09
Цитата(azesmcar @  22.1.2011,  22:04 Найти цитируемый пост)
auto_ptr должен удаляться в деструкторе класса X, 

нет... деструктор auto_ptr формируется в месте инстантирования тьфу использования.. и там должен быть виден нужный деструктор ..

Автор: azesmcar 22.1.2011, 23:10
Цитата(mes @  22.1.2011,  23:09 Найти цитируемый пост)
нет... деструктор auto_ptr формируется в месте инстанцирования.. и там должен быть виден нужный деструктор ..

формируется - да, но не вызывается.
а вызывается он уже там, где реализация видна.

код в первом посте нормально компилируется и работает, вопрос в том, что он перестает работать (точнее просто выдает warning) как только я пишу конструктор в заголовочном файле. А я не могу его писать в файле реализации, он шаблонный.

Добавлено через 5 минут и 16 секунд
Цитата(alexvs11 @  22.1.2011,  23:02 Найти цитируемый пост)
в немногих случаях, когда я видел применение auto_ptr это было связано с глюками, которых не было бы без его применения

и о чем это говорит?

Добавлено через 6 минут и 23 секунды
mes

http://www.gotw.ca/publications/mill12.htm
Цитата

A General Technique: Using the Pimpl Idiom

  //  Example 2: The general solution to
  //             Cargill's Widget Example
  //
  class Widget
  {
    // ...

  private:
    class WidgetImpl;
    auto_ptr<WidgetImpl> pimpl_;

    // ... provide destruction, copy construction
    //     and assignment that work correctly, or
    //     suppress them ...
  };

  // Then, typically in a separate
  // implementation file:
  //
  class Widget::WidgetImpl
  {
  public:
    // ...
    T1 t1_;
    T2 t2_;
  };

Автор: mes 22.1.2011, 23:17
http://forum.vingrad.ru/forum/topic-304001/unread-1/hl/auto_ptr/index.html

Автор: azesmcar 22.1.2011, 23:20
Цитата(mes @  22.1.2011,  23:17 Найти цитируемый пост)
http://forum.vingrad.ru/forum/topic-304001..._ptr/index.html 

тут говориться то, о чем пишу я и нет ответа на мой вопрос smile

Добавлено через 22 секунды
Цитата(baldina @  24.6.2010,  11:21 Найти цитируемый пост)
потому что именно в деструкторе ~CMain1() вызывается деструктор auto_ptr и вот в этой точке нужно полное определение CPrivate1, что бы правильно вызвать его деструктор.
т.е. CPrivate1 не надо показывать всем, просто функции, его использующие (в т.ч. ~CMain1()) должны быть в единице компиляции, где он известен 


Автор: mes 22.1.2011, 23:21
перечитал еще раз.. понял о чем речь.. да я как то криво читал, сорри.. сейчас обдумаю smile

Добавлено через 5 минут и 18 секунд
Цитата(azesmcar @  22.1.2011,  22:10 Найти цитируемый пост)
. А я не могу его писать в файле реализации, он шаблонный.

мм.. а деструктор шаблона тоже в хидере ?
 

Автор: azesmcar 22.1.2011, 23:28
Цитата(mes @  22.1.2011,  23:21 Найти цитируемый пост)
мм.. а деструктор шаблона тоже в хидере ?

вот именно что нет, только конструктор..деструктор то я могу перенести в реализацию. Исходник точно такой, как в первом посте (только конструктор шаблонный). Для получения ошибки достаточно заменить в коде из первого поста
Цитата

X();

на
Код

X() {}

и убрать реализацию из cpp файла.

ну в общем-то как я уже писал решение есть и довольно простое, делать new, delete самому, но  хотелось бы разобраться.

Автор: mes 23.1.2011, 00:02
полагаю VS формирует _pre-destructor_ там, где формируется конструктор..

Автор: azesmcar 23.1.2011, 07:10
Цитата(mes @  23.1.2011,  00:02 Найти цитируемый пост)
полагаю VS формирует _pre-destructor_ там, где формируется конструктор..

Это объясняет ошибку, но это странно, что он вызывает деструктор auto_ptr в этом pre деструкторе...а если а в своем деструкторе к нему обращусь?

Автор: azesmcar 23.1.2011, 08:28
упс...забыл проинициализировать smile 

ложная тревога

Добавлено @ 08:37
Цитата(mes @  23.1.2011,  00:02 Найти цитируемый пост)
полагаю VS формирует _pre-destructor_ там, где формируется конструктор..

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

в итоге получается вот это.
работает, но деструктор XImpl не вызывается. smile 

x.h
Код

#ifndef x_h_included
#define x_h_included

#include <memory>

class XImpl;
class X
{
public:
    X()
    {
        init();
    }
    ~X();
private:
    void init();
    std::auto_ptr<XImpl> impl;
};

#endif // x_h_included

ximpl.h
Код

#ifndef ximpl_h_included
#define ximpl_h_included

class XImpl
{
public:
    XImpl() {};
    ~XImpl() {};
    void foo() {}
};

#endif // ximpl_h_included

x.cpp
Код

#include "ximpl.h"
#include "x.h"

X::~X()
{
    impl->foo();
}

void X::init()
{
    impl.reset(new XImpl());
}


Автор: mes 23.1.2011, 12:22
Цитата(azesmcar @  23.1.2011,  07:28 Найти цитируемый пост)
проверил, в деструкторе к impl можно спокойно обращаться, т

ну так и должно оно быть smile


Цитата(azesmcar @  23.1.2011,  07:28 Найти цитируемый пост)
Почему в таком случае студия хочет деструктора там, где он ей не нужен?

наверно слово pre_destructor неправильно подобрал.. 
имелась ввиду такая условная схема:
Код

A::~A() 
${  
      {  ...  } // определенный пользователем деструктор
... // вызов деструкторов  дата-членов и базовых
$} 


Автор: azesmcar 23.1.2011, 12:35
Цитата(mes @  23.1.2011,  12:22 Найти цитируемый пост)
наверно слово pre_destructor неправильно подобрал.. 
имелась ввиду такая условная схема:

теперь понятнее..и насколько это соответствует стандарту? smile

Добавлено через 20 секунд
Цитата(mes @  23.1.2011,  12:22 Найти цитируемый пост)
ну так и должно оно быть 

ну вообще да, так предполагается smile 

Автор: mes 23.1.2011, 13:05
Цитата(azesmcar @  23.1.2011,  11:35 Найти цитируемый пост)
и насколько это соответствует стандарту? 

такая схема или место формирования деструктора ?

Автор: azesmcar 23.1.2011, 13:06
Цитата(mes @  23.1.2011,  13:05 Найти цитируемый пост)
такая схема или место формирования деструктора ?

больше всего интересует итог всего этого, т.е. такое поведение данного кода.

Автор: mes 23.1.2011, 14:10
насчет схемы отсюда можно сделать вывод :
Цитата

After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X’s direct members, the destructors for X’s direct base classes ...
...
A return statement (6.6.3) in a destructor might not directly return to the caller; before transferring control to the caller, the destructors for
the members and bases are called.

а когда формировать эту часть деструктора, в стандарте вроде не сказано .. 

Автор: azesmcar 24.1.2011, 06:08
Цитата(mes @  23.1.2011,  14:10 Найти цитируемый пост)
A return statement (6.6.3) in a destructor might not directly return to the caller; before transferring control to the caller, the destructors for
the members and bases are called.

ну это естественно.

Цитата(mes @  23.1.2011,  14:10 Найти цитируемый пост)
After executing the body of the destructor and destroying any automatic objects allocated within the body, a destructor for class X calls the destructors for X’s direct members, the destructors for X’s direct base classes ...

smile 
т.е. именно деструктор должен вызывать, деструкторы для базового классов и членов.

Цитата(mes @  23.1.2011,  12:22 Найти цитируемый пост)
// вызов деструкторов  дата-членов и базовых

тогда это не соответствует стандарту (если это конечно работает именно так)

Автор: xvr 24.1.2011, 14:57
Цитата(azesmcar @  23.1.2011,  08:28 Найти цитируемый пост)
проверил, в деструкторе к impl можно спокойно обращаться, т.е. удаляется объект после выхода из тела деструктора в файле реализации. Почему в таком случае студия хочет деструктора там, где он ей не нужен?
Возможно он ей нужен, что бы сгенерить код поддержки исключений, для раскрутки стека в возможном исключении после вызова конструктора std::auto_ptr<XImpl> (хотя его там и нет smile )
Т.е. то, что она (студия) сгенерила деструктор auto_ptr в конструкторе X::X(), еще не значит, что она его когда либо вообще позовет  smile 


Автор: mes 24.1.2011, 23:37
Цитата(azesmcar @  24.1.2011,  05:08 Найти цитируемый пост)
тогда это не соответствует стандарту (если это конечно работает именно так) 

не понял о чем речь..

Добавлено через 1 минуту и 49 секунд
Цитата(xvr @  24.1.2011,  13:57 Найти цитируемый пост)
Возможно он ей нужен, что бы сгенерить код поддержки исключений,

да, хороший повод для формирования деструктора..

Автор: azesmcar 25.1.2011, 06:11
Цитата(mes @  24.1.2011,  23:37 Найти цитируемый пост)
не понял о чем речь..

Вы описали поведение деструктора вот так

Цитата(mes @  23.1.2011,  12:22 Найти цитируемый пост)
A::~A() 
${  
      {  ...  } // определенный пользователем деструктор
... // вызов деструкторов  дата-членов и базовых
$} 

т.е. он формирует пре-деструктор (назовем его так) там, где определен конструктор. По стандарту
Цитата

a destructor for class X calls the destructors for X’s direct members

т.е. именно деструктор класса X должен вызывать деструкторы своих непосредственных членов, а не пре-деструктор.

Цитата(xvr @  24.1.2011,  14:57 Найти цитируемый пост)
Возможно он ей нужен, что бы сгенерить код поддержки исключений, для раскрутки стека в возможном исключении после вызова конструктора std::auto_ptr<XImpl> (хотя его там и нет  )
Т.е. то, что она (студия) сгенерила деструктор auto_ptr в конструкторе X::X(), еще не значит, что она его когда либо вообще позовет   

ну gcc ведь как-то работает, т.е. задача по сути решаемая. smile 

Автор: mes 25.1.2011, 09:41
Цитата(azesmcar @  25.1.2011,  05:11 Найти цитируемый пост)
т.е. он формирует пре-деструктор (назовем его так) там, где определен конструктор. По стандарту

если по стандарту, то он называется destructor, а то , что определяет пользователь destructor`s body..

Цитата(azesmcar @  25.1.2011,  05:11 Найти цитируемый пост)
т.е. именно деструктор класса X должен вызывать деструкторы своих непосредственных членов, а не пре-деструктор.

ну так разноглассие ввиду неправильно подобранных названий, предыдущий абзац "решает" эту проблему  smile 

ну а если еще и соответствовать пункту о return, то условно 
Код

A::A ()          __DTOR(A, 
{
    //  user defined
} 
)
//  при
#define __DTOR(cls,user_code) 
{
    struct l {
         static body    (cls this)  user_code
         static destroy (cls this) { ... }
    }
    l::body ();
    l::destroy();
}




Автор: xvr 25.1.2011, 11:35
Цитата(azesmcar @  25.1.2011,  06:11 Найти цитируемый пост)
ну gcc ведь как-то работает, т.е. задача по сути решаемая.

Сильно зависит от контекста. Например тут:
Код

class X {
 std::auto_ptr<Y> some;
 SomeClass obj_with_nontrivial_constructor;
public:
 X(Y* y) :some(y) {}
};
деструктор для Y реально понадобится, т.к. порядок конструирования членов - some а затем obj_with_nontrivial_constructor, и в случае исключения в конструкторе obj_with_nontrivial_constructor придется звать деструктор для Y.
А тут:
Код

class X {
 SomeClass obj_with_nontrivial_constructor;
 std::auto_ptr<Y> some;
public:
 X(Y* y) :some(y) {}
};
деструктор для Y не нужен, т.к. порядок конструирования членов обратный, и после конструктора auto_ptr исключение возникнуть не может (т.к. просто негде).  smile 

VS скорее всего не вдается в такие тонкости и просто лепит инстанс деструктора безусловно  smile 

Автор: azesmcar 25.1.2011, 12:02
Цитата(xvr @  25.1.2011,  11:35 Найти цитируемый пост)
Сильно зависит от контекста. Например тут:

в gcc нет никаких волнений по этому поводу, все деструкторы нормально вызываются.

Цитата(xvr @  25.1.2011,  11:35 Найти цитируемый пост)
деструктор для Y реально понадобится, т.к. порядок конструирования членов - some а затем obj_with_nontrivial_constructor, и в случае исключения в конструкторе obj_with_nontrivial_constructor придется звать деструктор для Y.

в моем примере там вообще ничего нету кроме auto_ptr<XImpl>. На эту тонкость студии тоже по всей видимости плевать smile 

Цитата(xvr @  25.1.2011,  11:35 Найти цитируемый пост)
VS скорее всего не вдается в такие тонкости и просто лепит инстанс деструктора безусловно   

Да, так и есть. smile 

Ну в общем уже примерно понятно почему и как..тему закрываю, всем спасибо (хотя дискуссию конечно можно продолжить) smile 

Автор: xvr 25.1.2011, 12:31
Цитата(azesmcar @  25.1.2011,  12:02 Найти цитируемый пост)
в gcc нет никаких волнений по этому поводу, все деструкторы нормально вызываются.
Увы, не нормально. Сделал тестовый пример:
Код

#include <memory>

class SomeClass {
public:
 SomeClass();
 ~SomeClass();
};

class Y;

class X {
 std::auto_ptr<Y> some;
 SomeClass obj_with_nontrivial_constructor;
public:
 X(Y* y) :some(y) {}
};

void q(Y* y)
{
 new X(y);
}
gcc 4.5.1. Деструктор для std::auto_ptr<Y> был сгенерен, но деструктора для Y в нем не позвали :(

Код

std::auto_ptr<Y>::~auto_ptr():
    .cfi_startproc
    pushq    %rbp
    .cfi_def_cfa_offset 16
    movq    %rsp, %rbp
    .cfi_offset 6, -16
    .cfi_def_cfa_register 6
    subq    $16, %rsp
    movq    %rdi, -8(%rbp)
    movq    -8(%rbp), %rax
    movq    (%rax), %rax
    movq    %rax, %rdi
    call    operator delete(void*)
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc


А вот если добавить описание класса Y (с деструктором), то его позовут -
Код

std::auto_ptr<Y>::~auto_ptr():
    .cfi_startproc
    pushq    %rbp
    .cfi_def_cfa_offset 16
    movq    %rsp, %rbp
    .cfi_offset 6, -16
    .cfi_def_cfa_register 6
    pushq    %rbx
    subq    $24, %rsp
    movq    %rdi, -24(%rbp)
    movq    -24(%rbp), %rax
    movq    (%rax), %rbx
    .cfi_offset 3, -24
    testq    %rbx, %rbx
    je    .L10
    movq    %rbx, %rdi
    call    Y::~Y()
    movq    %rbx, %rdi
    call    operator delete(void*)
.L10:
    addq    $24, %rsp
    popq    %rbx
    leave
    .cfi_def_cfa 7, 8
    ret
    .cfi_endproc
Так что это явно баг в gcc  smile 

Автор: azesmcar 25.1.2011, 12:39
xvr

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

Добавлено через 7 минут и 21 секунду
выкладываю свой пример

x.h
Код

#ifndef x_h_included
#define x_h_included

#include <memory>
#include <map>

class XImpl;
class X
{
    std::map<int, int> obj_with_nontrivial_constructor;
    std::auto_ptr<XImpl> impl;
private:
    void create_ximpl();
public:
    X()
    {
        this->create_ximpl();
    };
    ~X();
};

#endif // x_h_included


ximpl.h
Код

#ifndef ximpl_h_included
#define ximpl_h_included

#include <iostream>

class XImpl
{
public:
    XImpl();
    ~XImpl();
};
#endif // ximpl_h_included

x.cpp
Код

#include <iostream>
#include "ximpl.h"
#include "x.h"

void X::create_ximpl()
{
    impl.reset(new XImpl());
}

X::~X()
{
    std::cout << "X::~X()" << std::endl;
}

int main()
{
    X x;
}

ximpl.cpp
Код

#include <iostream>
#include "ximpl.h"

XImpl::XImpl()
{
    std::cout << "XImpl::XImpl()" << std::endl;
}
XImpl::~XImpl()
{
    std::cout << "XImpl::~XImpl()" << std::endl;
};


выводит
Цитата

X::~X()
XImpl::~XImpl()

на gcc version 4.5.1

Автор: xvr 25.1.2011, 12:49
Цитата(azesmcar @  25.1.2011,  12:39 Найти цитируемый пост)
Не совсем понял..можно выложить полный исходники тестового примера?

В примере деструктор Y не вызывается (вне зависимости от исключений и от чего то бы ни было  smile  )
Если раскоментарить тело класса Y в файле 1.cpp, то деструктор вызывается

Автор: xvr 25.1.2011, 12:49
Сборка: g++ 1.cpp 2.cpp

Автор: azesmcar 25.1.2011, 12:58
xvr

Это не баг, деструктор в файле 1.cpp явно недоступен, его там просто нет, как и описания всего класса. Ошибка gcc в том, что он не предупредил об этом (странно кстати). Тут кстати SomeClass и не нужен вовсе, он и без этого не будет вызываться. Все дело в том, что у тебя нет деструктора в классе X, а значит он будет сгенерирован компилятором, сгенерирован он будет естестественно в файле 1.cpp, где он никак не может видеть деструктора класса Y и соответственно вызвать его никак не может.

Добавлено через 4 минуты и 14 секунд
http://www.gotw.ca/publications/mill12.htm
Цитата

Aside: Note that if you use an auto_ptr member, then: a) you must either provide the definition of WidgetImpl with the definition of Widget, or if you want to keep hiding WidgetImpl you must write your own destructor for Widget even if it's a trivial destructor;[2] and b) you should also provide your own copy construction and assignment for Widget because normally you don't want transfer-of-ownership semantics for class members. If you have a different kind of smart pointer available, consider using that instead of auto_ptr, but the principles being described here remain important.

Автор: xvr 25.1.2011, 13:16
Цитата(azesmcar @  25.1.2011,  12:58 Найти цитируемый пост)
Ошибка gcc в том, что он не предупредил об этом (странно кстати).
Именно! В этом и ошибка, VS кстати предупреждает.

Цитата(azesmcar @  25.1.2011,  12:58 Найти цитируемый пост)
Тут кстати SomeClass и не нужен вовсе, он и без этого не будет вызываться.
Угу, это остаток от эксперимента с эксепшенами.

PS. Я добавил кое что к примеру, gcc по прежнему вполне счастлив, но результат зависит от порядка линковки файлов:
Код

[rakhvato@msteplxl41 qq1]$ g++ 2.cpp 1.cpp                                              
[rakhvato@msteplxl41 qq1]$ ./a.out 
~X()
~Y()
[rakhvato@msteplxl41 qq1]$  g++ 1.cpp 2.cpp                                              
[rakhvato@msteplxl41 qq1]$ ./a.out 
~X()

Вот такие пироги  smile 

Автор: azesmcar 25.1.2011, 13:18
Цитата(xvr @  25.1.2011,  13:16 Найти цитируемый пост)
Именно! В этом и ошибка, VS кстати предупреждает.

Да, в этом он ошибается smile 
Но по части кода ведет он себя как и ожидалось. А вот у меня ситуация немного другая, gcc работает, а VS - нет. smile

Добавлено через 53 секунды
Цитата(xvr @  25.1.2011,  13:16 Найти цитируемый пост)
Вот такие пироги   

вот это уже интереснее smile  smile 

Автор: xvr 25.1.2011, 13:37
Цитата(azesmcar @  25.1.2011,  13:18 Найти цитируемый пост)
А вот у меня ситуация немного другая, gcc работает, а VS - нет.

Ну в общем VS прав, но в данном случае он явно 'перебдел'  smile Так что можно отключить в этом месте этот варнинг и жить спокойно  smile 

Автор: azesmcar 25.1.2011, 13:39
Цитата(xvr @  25.1.2011,  13:37 Найти цитируемый пост)
Ну в общем VS прав, но в данном случае он явно 'перебдел'   Так что можно отключить в этом месте этот варнинг и жить спокойно   

Да дело то не в варнинге, деструктор на самом деле не вызывается. smile 

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)