вызов чистой виртуальной функции в деструкторе -- как быть?
От: konst  
Дата: 29.01.04 10:58
Оценка:
Студия 7.1. Стандарта в руках нет. Натолкнулся на такое:
class A
{
public:
  virtual ~A();
private:
  virtual void foo() = 0;
};

A::~A()
{
  foo();
}

Компилит, но не линкует, говорит, что
error LNK2019: unresolved external symbol "private: virtual void __thiscall A::foo(void)" (?foo@A@@EAEXXZ) referenced in function "public: virtual __thiscall A::~A(void)" (??1A@@UAE@XZ)
А если сделать так:
class A
{
public:
  virtual ~A();
private:
  virtual void foo() = 0;
  void Dtor();
};

void A::Dtor()
{
  foo();
}

A::~A()
{
  Dtor();
}

То и компилится и линкиется. Вот задумался, наверное, люди не зря сделали так, чтобы нельзя было чистую виртуальную функцию вызывать из деструктора, ну, напрмер, чтоб исключения не могло быть по причине того, что функция не реализована...Хорошее это решение через обёрточную функцию и не приведёт ли к проблемам в дальнейшем?
А сделал так вот почему: есть шаблон "наблюдатель", "субъект" вызывает у "подписчиков" при уничтожении OnDelete, что я в деструктор и запихнул; конкретный класс субъекта наследутся от интерфейса субъекта, а реализует все присущие субъекту функции шаблонный класс, ну, вот таким примерно образом:
// класс A остаётся
template <class A>
class B: public A
{
private:
  virtual void foo()
  {
     // какой-то код, одинаковый для всех наследников A
  }
};


Правильный это дизайн или нет, или как лучше?
Re: вызов чистой виртуальной функции в деструкторе -- как бы
От: davenger  
Дата: 29.01.04 11:04
Оценка:
Здравствуйте, konst, Вы писали:

<skipped>

В деструкторе (как и в конструторе) функции вызываются невиртуально, т.к. в момент вызова деструктора базового класа наследник уже учичтожен.
Re: вызов чистой виртуальной функции в деструкторе -- как бы
От: Mechanicus Беларусь  
Дата: 29.01.04 11:40
Оценка:
Здравствуйте, konst, Вы писали:

K>Студия 7.1. Стандарта в руках нет. Натолкнулся на такое:

Виртуальные функции из деструктора вообще не имеет смысла вызывать. Точнее никакой виртуальность ты от них не добъёшься.

K>
K>class A
K>{
K>public:
K>  virtual ~A();
K>private:
K>  virtual void foo() = 0;
K>};

K>A::~A()
K>{
K>  foo();
K>}
K>

K>Компилит, но не линкует, говорит, что
K>error LNK2019: unresolved external symbol "private: virtual void __thiscall A::foo(void)" (?foo@A@@EAEXXZ) referenced in function "public: virtual __thiscall A::~A(void)" (??1A@@UAE@XZ)
Вообще-то он должен ругаться га abstract virtual foo() called. VC конечно можно успокоить, дописав тело абстрактной функции, но имхо это не правильно.
K>А если сделать так:
K>
K>class A
K>{
K>public:
K>  virtual ~A();
K>private:
K>  virtual void foo() = 0;
K>  void Dtor();
K>};

K>void A::Dtor()
K>{
K>  foo();
K>}

K>A::~A()
K>{
K>  Dtor();
K>}
K>

K>То и компилится и линкиется. Вот задумался, наверное, люди не зря сделали так, чтобы нельзя было чистую виртуальную функцию вызывать из деструктора, ну, напрмер, чтоб исключения не могло быть по причине того, что функция не реализована...Хорошее это решение через обёрточную функцию и не приведёт ли к проблемам в дальнейшем?
А ты код исполнять то пробовал? Сразу бы все вопросы отпали.
тут явный pure virtual function call.
K>А сделал так вот почему: есть шаблон "наблюдатель", "субъект" вызывает у "подписчиков" при уничтожении OnDelete, что я в деструктор и запихнул; конкретный класс субъекта наследутся от интерфейса субъекта, а реализует все присущие субъекту функции шаблонный класс, ну, вот таким примерно образом:
K>
K>// класс A остаётся
K>template <class A>
K>class B: public A
K>{
K>private:
K>  virtual void foo()
K>  {
K>     // какой-то код, одинаковый для всех наследников A
K>  }
K>};
K>


K>Правильный это дизайн или нет, или как лучше?

Не будет это работать. Во-первых зачем класс B шаблонный? Не понятно как ты собираешься это использовать. И под сомнением необходимость виртуальной функции foo.
... << RSDN@Home 1.1.3 beta 1 >>
Re[2]: вызов чистой виртуальной функции в деструкторе -- как
От: konst  
Дата: 29.01.04 11:53
Оценка:
M>Не будет это работать. Во-первых зачем класс B шаблонный? Не понятно как ты собираешься это использовать. И под сомнением необходимость виртуальной функции foo.
struct I
{
virtual void f() = 0;
};
class A: public I
{
public:
  virtual void f()
  {
    // что то такое
    foo();
  }
private:
  virtual void foo() = 0;
};
class B: public I
{
public:
  virtual void f()
  {
    // что то ещё
    foo();
  }
private:
  virtual void foo() = 0;
};
template <class T>
class ImplFoo
{
private:
  virtual void foo()
  {
     // что то общее для A и B
  }
};

да такое часто встречается... дело не в надобности, а в том, что потребовалось в деструкторе, блин
Re: вызов чистой виртуальной функции в деструкторе -- как бы
От: Vamp Россия  
Дата: 29.01.04 12:12
Оценка: +1
Собственно, тебе уже объяснили, что все вызовы в конструткорах и деструкторах — статические. А почему нельзя вызывать твою f() в деструкторах производного класса? Ведь даже если ты напишешь код этой функции, то будет вызван именно он и никакие специфические действия по очистке потомков выполнены не будут.
Да здравствует мыло душистое и веревка пушистая.
Re[2]: вызов чистой виртуальной функции в деструкторе -- как
От: konst  
Дата: 29.01.04 13:04
Оценка:
Здравствуйте, Vamp, Вы писали:

V>Собственно, тебе уже объяснили, что все вызовы в конструткорах и деструкторах — статические. А почему нельзя вызывать твою f() в деструкторах производного класса? Ведь даже если ты напишешь код этой функции, то будет вызван именно он и никакие специфические действия по очистке потомков выполнены не будут.

не понял.
думал, как можно решить эту проблему:
1. можно сделать с наследованием, но криво -- из деструктора потомка вызывать функцию, которая вызывает виртуальную ту, что реализует этот потомок:
class A
{
protected:
  void OnDestruct()
  {
    f();
  }
private:
  virtual void f() = 0;
};
template <class A>
class B: public A
{
public:
  ~B()
  {
    A::OnDestruct();
  }
private:
  virtual void f()
  {
    // что-то
  }
};

2. забить на наследование и без него неплохо получается. вообще наследование для имплементации некоторых виртуальных методов, не связанных с этой функцией...
Re[3]: вызов чистой виртуальной функции в деструкторе -- как
От: konst  
Дата: 29.01.04 13:12
Оценка:
ага, понял нет, нельзя просто, т.к. в жизни она с параметром, о котором потомок не знает.
Re[3]: вызов чистой виртуальной функции в деструкторе -- как
От: Vamp Россия  
Дата: 29.01.04 13:31
Оценка:
K>1. можно сделать с наследованием, но криво -- из деструктора потомка вызывать функцию, которая вызывает виртуальную ту, что реализует этот потомок:
....
А зачем тебе вообще OnDestruct в таком случае? Вызывай прямо f из деструктора — о чем я и сказал.
Да здравствует мыло душистое и веревка пушистая.
Re: вызов чистой виртуальной функции в деструкторе -- как бы
От: Кодт Россия  
Дата: 29.01.04 17:01
Оценка:
Здравствуйте, konst, Вы писали:

Ну и в конструкторе то же самое можешь получить.
Виртуальные методы в конструкторе/деструкторе вызываются статически (именно чтобы не получить abstract function call). Для этого можно определить тело абстрактного метода:
class A
{
public:
  A() { method(); }
  virtual void method() = 0;
};
void A::method()
{ ..... }

Естественно, что другие методы не знают, вызваны они из ктора/дтора или откуда ещё — поэтому там уже динамический вызов виртуальных методов.

А поскольку в кторе/дторе объекту устанавливаются его родные VMT (а не финального класса), то имеем бабах.
class A
{
public:
  A()
  // vfptr = &A::vmt
  {
    method(); // A::method()
    invoke();
  }

  ~A()
  // vfptr = &A::vmt
  {
    method(); // A::method()
    invoke();
  }

  void invoke()
  {
    method(); // (this->*(this->vfptr->pmethod))();
  }

  virtual void method() = 0;
};

// если этого не будет, то линкер не удовлетворит жажду A::A(), A::~A()
void A::method() { ... }

class B : public A
{
public:

  B()
  // : A()
  // vfptr = &B::vmt
  {
    invoke();
  }

  ~B() // vfptr = &B::vmt
  {
    invoke();
  } // ~A()

  void method() { ... }
};
Перекуём баги на фичи!
Re[3]: вызов чистой виртуальной функции в деструкторе -- как
От: Tonal- Россия www.promsoft.ru
Дата: 29.01.04 19:36
Оценка:
А почему не сделать так:
K>
K>struct I
K>{
K>virtual void f() = 0;
K>};

K>template <class T>
K>class ImplFoo
K>{
K>private:
K>  void foo()
K>  {
K>     // что то общее для A и B
K>  }
K>};

K>class A: public I, private ImplFoo<A>
K>{
K>public:
K>  virtual void f()
K>  {
K>    // что то такое
K>    foo();
K>  }
K>};
K>class B: public I, private ImplFoo<B>
K>{
K>public:
K>  virtual void f()
K>  {
K>    // что то ещё
K>    foo();
K>  }
K>};
K>


К тому же избавились от ненужной виртуальности.
Да, класс ImplFoo вполне может быть и не шаблонным. ;-в
... << RSDN@Home 1.1.0 stable >>
Re[2]: вызов чистой виртуальной функции в деструкторе -- как
От: konst  
Дата: 30.01.04 07:14
Оценка: +1
Спасибо, я после того, как написал -- подумал и понял, что в деструкторе не выйдет, а жаль... однако переделок минимум, чтоб без наследования, так что...
Есть ещё вопросы

Поиском что-то не нашёл ответ -- это как вызвать из шаблона деструктор??? Воп пример:
struct A
{
  virtual ~A();
};

template <class A>
struct B: public A
{
  virtual ~B()
  {
    A::~A(); // очевидно, что так не выйдет
  }
};

А как надо?

А ещё где же обещанный конфиг
Автор: Кодт
Дата: 09.01.04
?
Re[3]: вызов чистой виртуальной функции в деструкторе -- как
От: Faust Россия  
Дата: 30.01.04 07:25
Оценка:
Здравствуйте, konst, Вы писали:

K>Спасибо, я после того, как написал -- подумал и понял, что в деструкторе не выйдет, а жаль... однако переделок минимум, чтоб без наследования, так что...

K>Есть ещё вопросы

K>Поиском что-то не нашёл ответ -- это как вызвать из шаблона деструктор??? Воп пример:

K>
K>struct A
K>{
K>  virtual ~A();
K>};

K>template <class A>
K>struct B: public A
K>{
K>  virtual ~B()
K>  {
K>    A::~A(); // очевидно, что так не выйдет
K>  }
K>};
K>

K>А как надо?

K>А ещё где же обещанный конфиг
Автор: Кодт
Дата: 09.01.04
?


Может такая конструкция поможет?
template <class TNode>
class TBaseDispatcher
{
public:
    TNode*                    m_pCursor;
    size_t                    m_nCountNodes;

public:
    TBaseDispatcher(void)
        : m_pCursor(NULL)
        , m_nCountNodes(0) { }
    virtual ~TBaseDispatcher(void) { }

//private:
    virtual void            Allocate(const size_t& nCountNodes)
    { m_pCursor = new TNode[nCountNodes]; m_nCountNodes = nCountNodes; }
    virtual void            Free(void) { delete[] m_pCursor; }
};

template <class TNode, class TDispatcher = TBaseDispatcher<TNode> >
class TBaseContainer : public TDispatcher
{
public:
    TNode*                    m_pBeginNode;

public:
    TBaseContainer(void)
        : TDispatcher()
        , m_pBeginNode(m_pCursor) {}
    explicit TBaseContainer(const size_t& nCountNodes)
        : TDispatcher()
    { Allocate(nCountNodes); m_pBeginNode = m_pCursor; }
    virtual ~TBaseContainer(void) { m_pCursor = m_pBeginNode; Free(); }
};
Мой компьютер прогоняет бесконечный цикл за 9 секунд, но, мне кажется, он мог бы сделать это быстрее...
Re[4]: вызов чистой виртуальной функции в деструкторе -- как
От: konst  
Дата: 30.01.04 08:09
Оценка:
Здравствуйте, Faust, Вы писали:


F>Может такая конструкция поможет?

F>
F>template <class TNode>
F>class TBaseDispatcher
F>{
F>public:
F>    TNode*                    m_pCursor;
F>    size_t                    m_nCountNodes;

F>public:
F>    TBaseDispatcher(void)
F>        : m_pCursor(NULL)
F>        , m_nCountNodes(0) { }
F>    virtual ~TBaseDispatcher(void) { }

F>//private:
F>    virtual void            Allocate(const size_t& nCountNodes)
F>    { m_pCursor = new TNode[nCountNodes]; m_nCountNodes = nCountNodes; }
F>    virtual void            Free(void) { delete[] m_pCursor; }
F>};

F>template <class TNode, class TDispatcher = TBaseDispatcher<TNode> >
F>class TBaseContainer : public TDispatcher
F>{
F>public:
F>    TNode*                    m_pBeginNode;

F>public:
F>    TBaseContainer(void)
F>        : TDispatcher()
F>        , m_pBeginNode(m_pCursor) {}
F>    explicit TBaseContainer(const size_t& nCountNodes)
F>        : TDispatcher()
F>    { Allocate(nCountNodes); m_pBeginNode = m_pCursor; }
F>    virtual ~TBaseContainer(void) { m_pCursor = m_pBeginNode; Free(); }
F>};
F>


Хммм, а пример использования можно? А то не понял, для чего подойдёт?
Re[4]: вызов чистой виртуальной функции в деструкторе -- как
От: konst  
Дата: 30.01.04 08:16
Оценка:
Во многом подходит такой вариант Я про то, что делал недописал малость:
struct I
{
virtual void f() = 0;
virtual void g() = 0;
};
class A: public I
{
public:
  virtual void f()
  {
    // что то такое
    foo();
  }
private:
  virtual void foo() = 0;
};
class B: public I
{
public:
  virtual void f()
  {
    // что то ещё
    foo();
  }
private:
  virtual void foo() = 0;
};
template <class T>
class ImplFoo
{
private:
  virtual void foo()
  {
     // что то общее для A и B
  }
  virtual void g()
  {
    // имплементация g(), одинаковая для A и B
  }
};
Re[5]: вызов чистой виртуальной функции в деструкторе -- как
От: Faust Россия  
Дата: 30.01.04 08:38
Оценка:
Здравствуйте, konst, Вы писали:

K>Хммм, а пример использования можно? А то не понял, для чего подойдёт?


template <class TNode>
class TBaseDispatcher
{
public:
    TNode*                    m_pCursor;
    size_t                    m_nCountNodes;

public:
    TBaseDispatcher(void)
        : m_pCursor(NULL)
        , m_nCountNodes(0) { }
    virtual ~TBaseDispatcher(void) { }

//private:
    // Вот твои чисто виртуальные функции
    virtual void            Allocate(const size_t& nCountNodes) = 0;
    virtual void            Free(void) = 0;
};
template <class TNode, class TDispatcher = TBaseDispatcher<TNode> >
class TBaseContainer : public TDispatcher
{
public:
    TNode*                    m_pBeginNode;

public:
    TBaseContainer(void)
        : TDispatcher()
        , m_pBeginNode(m_pCursor) {}
    explicit TBaseContainer(const size_t& nCountNodes)
        : TDispatcher()
    // Вызов твоих виртуальных функций, а именно их конкретных реализаций, 
    // ведь именно этого ты добиваешся
    // Обрати внимание, вызов происходит из базового класса
    { Allocate(nCountNodes); m_pBeginNode = m_pCursor; }
    virtual ~TBaseContainer(void) { m_pCursor = m_pBeginNode; Free(); }
};
template <class TNode>
class TArrayDispatcher : public TBaseDispatcher<TNode>
{
public:
    TArrayDispatcher(void)
        : TBaseDispatcher<TNode>() { }
    virtual ~TArrayDispatcher(void) { }

    // А здесь ты их переопределяешь как хочешь
    virtual void            Allocate(const size_t& nCountNodes)
    { m_pCursor = new TNode[nCountNodes]; m_nCountNodes = nCountNodes; }
    virtual void            Free(void) { delete[] m_pCursor; }
};
template <class TNode, class TDispatcher = TArrayDispatcher<TNode> >
class TSafeArray : private TBaseContainer<TNode, TDispatcher>
{
public:
    TSafeArray(void)
        : TBaseContainer<TNode, TDispatcher>() {}
    explicit TSafeArray(const size_t& nCountNodes)
        : TBaseContainer<TNode, TDispatcher>(nCountNodes) { }
    virtual ~TSafeArray(void) { }
};
Мой компьютер прогоняет бесконечный цикл за 9 секунд, но, мне кажется, он мог бы сделать это быстрее...
Re[6]: вызов чистой виртуальной функции в деструкторе -- как
От: konst  
Дата: 30.01.04 09:16
Оценка:
Так, я действительно хочу понять, что ты мне пишешь, но всё равно пока не получается Я просил пример использования. То, что я хотел бы, выглядело бы так:
template <class T>
struct I1
{
  tyfedef T* Param;
  virtual void g(T) = 0;
  virtual ~I() {}
};

struct I2: public I1<T1>
{
  virtual void f() = 0;
};

struct I3: public I1<T2>
{
  virtual void h() = 0;
};


class A: public I2
{
public:
  virtual void f()
  {
    // что-то делаю
    foo(p1);
  }
  virtual ~A()
  {
    foo(p2); // так нельзя! (а очень хотелось)
  }
private:
  virtual void foo(Param) = 0; // только объявление
};

class B: public I3
{
public:
  virtual void h()
  {
    // что-то делаю
    foo(p1);
  }
private:
  virtual void foo(Param) = 0; // только объявление
};

template <class T1, class T2> class ImplSome: public T1
{
public:
  virtual void g(T2)
  {
     // имплементация чего-то
  }
private:
  virtual void foo(typename T1::Param p)
  {
    // имплементация
  }
};

// всё это заканчивается так
typedef ImplSome<A, T1> AImpl;
typedef ImplSome<B, T2> BImpl;

Мне бы хотелось просто сразу двух зайцев убить: сделать в ImplSome реализацию g() публичного интерфейса и одновременно реализацию функции foo закрытого интерфейса, только беда в том, что мне её надо вызвать в деструкторе и всё, а как можно для тех же целей использовать тобой написанное?
Возможно, вот такой подход, как я описал, не является оптимальным...
Re[7]: вызов чистой виртуальной функции в деструкторе -- как
От: Faust Россия  
Дата: 30.01.04 09:56
Оценка: 3 (1)
Здравствуйте, konst, Вы писали:

K>Так, я действительно хочу понять, что ты мне пишешь, но всё равно пока не получается Я просил пример использования.


template <class T>
struct I
{
    virtual ~I() {}

    virtual void g(T) = 0;
    virtual void foo(T*) = 0;
};

template <class T>
struct I1: public I<T>
{
    I1() {}
    virtual void f() = 0;
};

template <class T>
struct I2: public I<T>
{
    virtual void h() = 0;
};

template <class T>
class IImpl: public I1<T>
{
public:
  IImpl() : I1<T>() {}
  virtual void f() {  }
  virtual void g(T) {  }
  virtual void foo(T*) {  }
};

template <class T, class I = IImpl<T> >
class AImpl: public I
{
public:
  T*        param;
  AImpl() : I() {}
  virtual ~AImpl() { foo(param); }
};
Мой компьютер прогоняет бесконечный цикл за 9 секунд, но, мне кажется, он мог бы сделать это быстрее...
Re[8]: вызов чистой виртуальной функции в деструкторе -- как
От: konst  
Дата: 30.01.04 10:36
Оценка:
Идею понял спасиба.
Re[4]: вызов чистой виртуальной функции в деструкторе -- как
От: dad  
Дата: 30.01.04 14:33
Оценка:
K>ага, понял нет, нельзя просто, т.к. в жизни она с параметром, о котором потомок не знает.

как это виртальная функция с параметром о котором потомок не знает?
Веру-ю-у! В авиацию, в научную революци-ю-у, в механизацию сельского хозяйства, в космос и невесомость! Веру-ю-у! Ибо это объективно-о! (Шукшин)
Re[5]: вызов чистой виртуальной функции в деструкторе -- как
От: konst  
Дата: 30.01.04 14:46
Оценка:
dad>как это виртальная функция с параметром о котором потомок не знает?
Простите моё костноязычие, значение параметра, которое надо передать этой функции.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.