Russian Qt Forum

Программирование => С/C++ => Тема начата: lcs-perm от Май 18, 2013, 07:20



Название: Использование чистой виртуальной функции в конструкторе
Отправлено: lcs-perm от Май 18, 2013, 07:20
Добрый день.
Пытаюсь разобраться со следующим кодом:
Код:
#include <iostream>

using namespace std;

class AbstractClass
{
protected:
  virtual void AbstractFunc() = 0;
public:
  AbstractClass();
};

AbstractClass::AbstractClass()
{
  AbstractFunc();
}

int main()
{
  return 0;
}
На этот код при компиляции мне выдается сразу куча сообщений:
Код:
In constructor 'AbstractClass::AbstractClass()':
warning: abstract virtual 'virtual void AbstractClass::AbstractFunc()' called from constructor
undefined reference to `AbstractClass::AbstractFunc()'
undefined reference to `AbstractClass::AbstractFunc()'
collect2: ld returned 1 exit status
Причем, если сделать AbstractFunc() просто виртуальной - все становится просто замечательно.
Помогите разобраться, пожалуйста


Название: Re: Использование чистой виртуальной функции в конструкторе
Отправлено: Странник от Май 18, 2013, 08:07
ну а что вы ожидали получить, вызывая чисто виртуальную функцию virtual void AbstractClass::AbstractFunc() = 0, реализация для которой отсутствует? вы должны реализовать перегруженную функцию в наследнике AbstractClass и вызывать ее в конструкторе наследника для получения желаемого эффекта. также вы сможете обращаться к ней посредством указателя типа базового класса на объект наследника.


Название: Re: Использование чистой виртуальной функции в конструкторе
Отправлено: lcs-perm от Май 18, 2013, 08:30
ну а что вы ожидали получить, вызывая чисто виртуальную функцию virtual void AbstractClass::AbstractFunc() = 0, реализация для которой отсутствует?
Немного модифицировал код, чтоб компилировалось без ошибок
Код:
class AbstractClass
{
protected:
  virtual void AbstractFunc() { cout << "AbstractClass" << endl; }
public:
  AbstractClass();
};

class RealClass: public AbstractClass
{
protected:
  void AbstractFunc() { cout << "RealClass\n" << endl; }
};

AbstractClass::AbstractClass()
{
  AbstractFunc();
}

int main()
{
  RealClass rc;
  return 0;
}
На выходе получаю "AbstractClass". А хотелось бы получить "RealClass"


Название: Re: Использование чистой виртуальной функции в конструкторе
Отправлено: Majestio от Май 18, 2013, 08:33
У наследника функция уже не виртуальная?


Название: Re: Использование чистой виртуальной функции в конструкторе
Отправлено: lcs-perm от Май 18, 2013, 08:36
У наследника функция уже не виртуальная?
Почему не виртуальная. Виртуальность же наследуется.
В любом случае - добавление virtual в наследника ничего не дает


Название: Re: Использование чистой виртуальной функции в конструкторе
Отправлено: Bepec от Май 18, 2013, 08:42
Сталкивался с такой хотелкой :)

Не пытайтесь вызвать функцию из конструктора - ничего не получится.

НО.

Если вызвать её QMetaObject::invokeMethod(this, "AbstractFunc"); то тогда всё пройдет так, как вы хотите. Функция в таком случае должна быть слотом.

Вкратце - в конструкторе у вас её нет. Но если оставить в очереди событий вызов этой функции, он выполнится сразу после конструктора и функция будет уже не предка, а наследника.

PS в С++ такое реализовать будет очень трудно, а в Qt с помощью сигнал-слотов более менее легко.


Название: Re: Использование чистой виртуальной функции в конструкторе
Отправлено: Majestio от Май 18, 2013, 08:43
Я знак вопроса лишний поставил. При вызове виртуальной функции из конструктора вызывается та версия, которая определена в данном классе или базового класса, если она в данном классе не перегружена. Вроде так. Для конструктора - формально она не виртуальная.


Название: Re: Использование чистой виртуальной функции в конструкторе
Отправлено: lcs-perm от Май 18, 2013, 09:15
Bepec. Ну у меня вопрос скорее даже не как реализовать, а скорее из разряда "хочется понять" (хотя и реализовать, конечно, хочется).
Majestio. Попробую поизучать таблицы виртуальных функций C++. Может и пойму, отчего так


Название: Re: Использование чистой виртуальной функции в конструкторе
Отправлено: mutineer от Май 18, 2013, 09:39
Так происходит потому, что во время выполнения конструктора таблица виртуальных функций инициализирована только для текущего класса и его базовых классов. Никакой информации про таблицу виртуальных функций наследников еще нет, поэтому будет вызываться функция текущего конструируемого класса


Название: Re: Использование чистой виртуальной функции в конструкторе
Отправлено: Igors от Май 18, 2013, 10:28
Почему не виртуальная. Виртуальность же наследуется.
В любом случае - добавление virtual в наследника ничего не дает
Дает, но только не в конструкторе/деструкторе базового класса - в этот момент порожденный объект как бы еще не создан (или уже разрушен). Кстати sizeof() также вернет размер базового (а не порожденного) в этот момент.

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


Название: Re: Использование чистой виртуальной функции в конструкторе
Отправлено: mutineer от Май 18, 2013, 10:30
Igors А что дает добавление virtual в наследнике?


Название: Re: Использование чистой виртуальной функции в конструкторе
Отправлено: Igors от Май 18, 2013, 11:00
Igors А что дает добавление virtual в наследнике?
Имеется ввиду "добавление виртуальной ф-ции в наследнике", а не написание для нее слова "virtual", его можно писать или нет - дело вкуса, все равно будет виртуальной.

Откуда такое горячее желание "поймать" на незнании азов? :) Хотите поизучать язык - давайте вместе. Напр
Так происходит потому, что во время выполнения конструктора таблица виртуальных функций инициализирована только для текущего класса и его базовых классов. Никакой информации про таблицу виртуальных функций наследников еще нет, поэтому будет вызываться функция текущего конструируемого класса
А как это протестировать/распечатать?


Название: Re: Использование чистой виртуальной функц
Отправлено: mutineer от Май 18, 2013, 11:43
Откуда такое горячее желание "поймать" на незнании азов? :) Хотите поизучать язык - давайте вместе. Напр

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


Название: Re: Использование чистой виртуальной функции в конструкторе
Отправлено: Old от Май 18, 2013, 12:17
А как это протестировать/распечатать?
Для gcc можно так:
Код
C++ (Qt)
#include <iostream>
 
class Base
{
public:
       Base()
       {
               void *vtbl = reinterpret_cast<void**>( this )[ 0 ];
               std::cout << "Constructor Base vtbl = " << vtbl << std::endl;
 
               method();
       };
 
       virtual void    method()
       {
               void *vtbl = reinterpret_cast<void**>( this )[ 0 ];
               std::cout << "Base::method vtbl = " << vtbl << std::endl;
       }
};
 
class Derived : public Base
{
public:
       Derived()
       {
               void *vtbl = reinterpret_cast<void**>( this )[ 0 ];
               std::cout << "Constructor Derived vtbl = " << vtbl << std::endl;
       };
 
       virtual void    method()
       {
               void *vtbl = reinterpret_cast<void**>( this )[ 0 ];
               std::cout << "Derived::method vtbl = " << vtbl << std::endl;
       }
};
 
int main()
{
       std::cout << "----- Build base -----" << std::endl;
       Base base;
       std::cout << "----- Build derived -----" << std::endl;
       Derived derived;
}
 


Название: Re: Использование чистой виртуальной функции в конструкторе
Отправлено: Igors от Май 18, 2013, 14:14
Для gcc можно так:
У меня аналогично (несущественная разница в деталях). Правда никакой он не void (не адрес) а, полагаю, индекс/ID (хорошо видно напр в 64). Хотя это не документировано - никогда не видел др реализации ни в одном компиляторе


Название: Re: Использование чистой виртуальной функции в конструкторе
Отправлено: Old от Май 18, 2013, 15:06
Правда никакой он не void (не адрес) а, полагаю, индекс/ID (хорошо видно напр в 64). Хотя это не документировано - никогда не видел др реализации ни в одном компиляторе
Не знаю куда вы смотрите, но это именно адрес таблицы, в которой лежат адреса функций (в x86_64 в том числе).

Код
C++ (Qt)
       void **vtbl = reinterpret_cast<void***>( this )[ 0 ];
       while( *vtbl )
               std::cout << *vtbl++ << std::endl;
 


Название: Re: Использование чистой виртуальной функции в конструкторе
Отправлено: CuteBunny от Май 22, 2013, 14:43
Добрый день.
Пытаюсь разобраться со следующим кодом:
Код:
#include <iostream>

using namespace std;

class AbstractClass
{
protected:
  virtual void AbstractFunc() = 0;
public:
  AbstractClass();
};

AbstractClass::AbstractClass()
{
  AbstractFunc();
}

int main()
{
  return 0;
}
На этот код при компиляции мне выдается сразу куча сообщений:
Код:
In constructor 'AbstractClass::AbstractClass()':
warning: abstract virtual 'virtual void AbstractClass::AbstractFunc()' called from constructor
undefined reference to `AbstractClass::AbstractFunc()'
undefined reference to `AbstractClass::AbstractFunc()'
collect2: ld returned 1 exit status
Причем, если сделать AbstractFunc() просто виртуальной - все становится просто замечательно.
Помогите разобраться, пожалуйста

Добавлю свои 5коп.
В принципе вы можете определить чисто виртуальную функцию, тогда в этом примере компилятор ошибку не выдаст, но предупреждение все равно будет кидать.

Код
C++ (Qt)
#include <iostream>
 
using namespace std;
 
class AbstractClass
{
protected:
 virtual void AbstractFunc() = 0;
public:
 AbstractClass();
};
 
void AbstractClass::AbstractFunc() {}
 
AbstractClass::AbstractClass()
{
 AbstractFunc();
}
 
int main()
{
 return 0;
}
 

Хммм, интересно, если чисто виртуальную функцию можно определить, то в теории её можно вызвать?...


Название: Re: Использование чистой виртуальной функции в конструкторе
Отправлено: lcs-perm от Май 24, 2013, 21:28
После непродолжительных раздумий решил для себя обозначенную в теме проблему следующим образом
Код:
#include <iostream>

using namespace std;

class AbstractClass
{
public:
  virtual void AbstractFunc() = 0;
};
template <class TChild> AbstractClass* AbstractClass_Create(AbstractClass *instance)
{
  TChild* result = (TChild*)instance;
  result->AbstractFunc();
  return result;
}
AbstractClass* AbstractClass_Create(AbstractClass *instance);

class RealClass01: public AbstractClass
{
friend AbstractClass* AbstractClass_Create<RealClass01>(AbstractClass *instance);
protected:
  void AbstractFunc() { cout << "RealClass01" << endl; }
  virtual void RealFunc() = 0;
};
template <class TChild> RealClass01* RealClass01_Create(RealClass01 *instance)
{
  TChild* result = (TChild*)AbstractClass_Create<RealClass01>(instance);
  result->RealFunc();
  return result;
}
RealClass01* RealClass01_Create(RealClass01 *instance);

class FactClass01: public RealClass01
{
friend RealClass01* RealClass01_Create<FactClass01>(RealClass01 *instance);
protected:
  void RealFunc() { cout << "FactClass01" << endl; }
};

class RealClass02: public AbstractClass
{
friend AbstractClass* AbstractClass_Create<RealClass02>(AbstractClass *instance);
protected:
  void AbstractFunc() { cout << "RealClass02" << endl; }
};

int main()
{
  AbstractClass *fc_01 = RealClass01_Create<FactClass01>(new FactClass01);
  AbstractClass *rc_02 = AbstractClass_Create<RealClass02>(new RealClass02);
  delete fc_01;
  delete rc_02;
  return 0;
}
В результате запуска этого приложения получаю такой вывод:
Код:
RealClass01
FactClass01
RealClass02
Есть, конечно, некоторая громоздкость, но на вскидку особых недостатков в этом решении я не заметил.
Хотелось бы обсудить, чтобы во-первых - эти самые недостатки увидеть; и, во-вторых - может найдется более приятное для глаза решение


Название: Re: Использование чистой виртуальной функции в конструкторе
Отправлено: Igors от Май 25, 2013, 09:53
После непродолжительных раздумий решил для себя обозначенную в теме проблему ..
..может найдется более приятное для глаза решение
Если поубирать ненужные template и поработать с именами - для глаза будет приятнее. В Вашем решении ничего нового не видно (что не значит "плохо"). После того как все конструкторы отработали - любые виртуалы выполняются "как доктор прописал".


Название: Re: Использование чистой виртуальной функции в конструкторе
Отправлено: lcs-perm от Май 27, 2013, 20:49
Если поубирать ненужные template и поработать с именами - для глаза будет приятнее. В Вашем решении ничего нового не видно (что не значит "плохо"). После того как все конструкторы отработали - любые виртуалы выполняются "как доктор прописал".
Ну на оригинальность я и не претендую особо. А вот насчет ненужных template'ов хотелось бы поподробнее.
Мне кажется, они обеспечивают возможность не переписывать дружественный конструктор отдельно для каждого класса-наследника. Т.е. именно так, как хочется - один абстрактный конструктор на всех наследников.
Или я чего то не понимаю?


Название: Re: Использование чистой виртуальной функции в конструкторе
Отправлено: Igors от Май 28, 2013, 09:57
А вот насчет ненужных template'ов хотелось бы поподробнее.
Мне кажется, они обеспечивают возможность не переписывать дружественный конструктор отдельно для каждого класса-наследника. Т.е. именно так, как хочется - один абстрактный конструктор на всех наследников.
Этого не видно
Код:
  AbstractClass *fc_01 = RealClass01_Create<FactClass01>(new FactClass01);
  AbstractClass *rc_02 = AbstractClass_Create<RealClass02>(new RealClass02);
Зачем нужно несколько template и оберток если никакой общности все равно не достигается? Т.е. один класс создаете одним template, другой другим. Не проще ли было "в стиле начинающего"
Код
C++ (Qt)
 AbstractClass *fc_01 = new FactClass01;
 fc_01->AbstractFunc();
 AbstractClass *rc_02 = new RealClass02;
 fc_02->AbstractFunc();
 
Что хотя и примитивно, но куда яснее и короче. Если же метод "неотъемлемая часть" конструктора, то сделать его невиртуальным, при этом виртуальный может просто его вызвать


Название: Re: Использование чистой виртуальной функции в конструкторе
Отправлено: lcs-perm от Май 28, 2013, 16:55
Ну, в классе RealClass01 я использовал абстрактный метод, чтобы проверить абстрактное наследование с глубиной больше чем 1.
А если бы в RealClass01 не было абстрактного метода, то код мог бы быть таким
Код:
AbstractClass *rc_01 = AbstractClass_Create<RealClass01>(new RealClass01);
AbstractClass *rc_02 = AbstractClass_Create<RealClass02>(new RealClass02);
И был бы только один раз написанный конструктор-обертка, который бы в дальнейшем объявлялся всем наследникам как дружественный.
Конечно, когда конструктор состоит из одного оператора, выгода не очевидна. А вот для достаточно сложных случаев все будет выглядеть совсем по другому.

Ну ладно. Спасибо Вам за потраченное время. По крайней мере, у меня была возможность сравнить все плюсы и минусы нескольких подходов.


Название: Re: Использование чистой виртуальной функции в конструкторе
Отправлено: ctin от Июнь 07, 2013, 13:00
Добавлю от себя. Сталкивался с подобной проблемой, когда была куча диалоговых окон с различным функционалом, который задавался при инициализации.
Решил так:

Код:
{
    AbstractDialog  *pDialog = createDialog("testName");
    if(pDialog->initFunc()) //это виртуальный метод.
        pDialog->exec(); //в конструкторе флаг DeleteOnClose
    else
        delete pDialog;
}