Russian Qt Forum
Май 21, 2024, 20:53 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1] 2   Вниз
  Печать  
Автор Тема: Копирование структур с укзателями  (Прочитано 9582 раз)
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« : Июль 12, 2016, 10:23 »

Добрый день

Есть структура
Код
C++ (Qt)
struct CData {
int mID;
QString mName;
..
AbstractClass * mData;
};
Где реально член mData = указателю на одного из наследников абстрактного класса AbstractClass. Как удобно (или лучше всего) скопировать экземпляр CData?
 
Спасибо
« Последнее редактирование: Июль 12, 2016, 10:53 от Igors » Записан
Racheengel
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2679


Я работал с дискетам 5.25 :(


Просмотр профиля
« Ответ #1 : Июль 12, 2016, 11:32 »

Зависит от того, нужно ли шарить mData между структурами или надо создавать каждый раз копию объекта.
Если последнее, то AbstractClass должен иметь что-то типа virtual AbstractClass* clone(), и все его наследники - соответствующие имплементации, чтобы гарантированно создавать копию правильного потомка.
Записан

What is the 11 in the C++11? It’s the number of feet they glued to C++ trying to obtain a better octopus.

COVID не волк, в лес не уйдёт
_Bers
Бывалый
*****
Offline Offline

Сообщений: 486


Просмотр профиля
« Ответ #2 : Июль 12, 2016, 22:26 »

Как удобно (или лучше всего) скопировать экземпляр CData?

конструктором копии по умолчанию, очевидно жеж.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #3 : Июль 13, 2016, 06:32 »

AbstractClass должен иметь что-то типа virtual AbstractClass* clone(),
Так сейчас и сделано, только Clone с большой буквы  Улыбающийся

конструктором копии по умолчанию, очевидно жеж.
Очевидно-то оно очевидно, только хлопотливо - virtual заводи, конструктор копирования и оператор присваивания перекрывай. Думал может какой-то "вумный указатель" позволит достичь того же более компактно - но видимо нет.
Записан
_Bers
Бывалый
*****
Offline Offline

Сообщений: 486


Просмотр профиля
« Ответ #4 : Июль 13, 2016, 07:51 »

Очевидно-то оно очевидно, только хлопотливо - virtual заводи, конструктор копирования и оператор присваивания перекрывай. Думал может какой-то "вумный указатель" позволит достичь того же более компактно - но видимо нет.

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

после копирования,
mData обоих экземпляров будет хранить одно и тоже значение.

если вам нужно клонировать наследника,
то это нужно сообщать в изначальной постановке задачи.

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

то есть, вместо:
Код:
AbstractClass * mData;

использовать:

Код:
wrapper<AbstractClass> mData;

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

стандартные смарт-поинтеры из коробки такое не умеют.

но вооружившись паттерном type erasure,
вы легко сможете завелосипедить необходимую вам функциональность.

здесь самое главное - определиться с принципов владения ресурсом смарта:
он должен вести себя подобно unique_ptr, или shared_ptr ?

или же он, как приличный, уважающий себя враппер,
должен олицетворять собой "объект по значению" ?


классический дизайн подобного рода механизмов:
Код:
{ // example

// AbstractClass может не иметь clone,
// и не иметь виртуального деструктора.
// но если он хочет клонироваться - наследники
// должны иметь конструктор копии явный, или дефолтный
wrapper<AbstractClass> obj1;

// захват ресурса
// создается конкретный наследник
// некоторые реализации при этом не используют кучу
// params соответствует аргументам конструктора
obj1.make<Concrete>(params);

// операция клонирования реализована в самом wrapper
// нет нужды переопределять виртуальные методы в каждом наследнике
// при помощи паттерна type erasure
// враппер "запомнил" тип ресурса, который он захватил
// поэтому, он может звать конструкторы/диструкторы/прочие методы
// наследника напрямки, минуя его базовый интерфейс
wrapper<AbstractClass> obj2 = obj1.clone();

} // example

// вот здесь время жизни врапперов завершилось
// и они ликвидировали свои ресурсы,
// вызвав диструкторы самих наследников
// поэтому очистка будет корректна
// даже если диструктор базового класса - не виртуальный


пример из стандартной библиотеки:
http://rextester.com/MSFNN7783

Код:
#include <iostream>
#include <memory>

struct base
{
    base() { std::cout<<"base: ctor\n"; }
    
    // ничайно забыли сделать его виртуальным
    ~base() { std::cout<<"base: dtor\n"; }
    
    virtual void foo()const = 0;
};

struct der: base
{
    der() { std::cout<<"der: ctor\n"; }
    
    // TODO: внимание! формально, здесь потенциальное UB
    // только для ознакомления!
    // стандарт требует наличие виртуального диструктора
    // в базовом интерфейсе, если предполагается использовать полиморфизм
    ~der() { std::cout<<"der: dtor\n"; }
    
    virtual void foo()const {std::cout<<"der: foo\n"; }
        
};


int main()
{
    std::cout << "Hello, world!\n";
    
    {
        // ничего ни о каких наследниках не знает
        using wrapper = std::shared_ptr<base>;
    
        // зато про наследника знает функция захвата ресурса
        wrapper obj1 = std::make_shared<der>();
        
        // она построит особую разновидность wrapper
        // внутри которого будет внедрен helper,
        // реализуюющий type erasure
        // он будет "помнить" настоящий тип ресурса
        // и при самоуничтожении
        // позовет корректный диструктор напрямки
        // минуя базовый интерфейс
        
    }
    
    // вот здесь мы увидим корректный запуск диструктора наследника
    
    std::cout << "Finish\n";
}

к сожалению, стандартные смарт-поинтеры не поддерживают clone из коробки.
поэтому, приходится велосипедить своё.

« Последнее редактирование: Июль 13, 2016, 07:54 от _Bers » Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #5 : Июль 13, 2016, 10:03 »

если вам нужно клонировать наследника,
то это нужно сообщать в изначальной постановке задачи.
Если нужно шарить - то просто шаред пойнтер, и все дела. Думаю это и так всем ясно. Разумеется речь о клонировании.

здесь самое главное - определиться с принципов владения ресурсом смарта:
он должен вести себя подобно unique_ptr, или shared_ptr ?

или же он, как приличный, уважающий себя враппер,
должен олицетворять собой "объект по значению" ?
С "олицетворять" с Вашей помощью вроде разобрался. Вот сочинил пример
Код
C++ (Qt)
#include <iostream>
#include <memory>
 
struct CBase {
CBase( void ) { std::cout << "Consruct Base\n"; }
  ~CBase( void ) { std::cout << "Destruct Base\n"; }
 
   virtual const char * Name( void ) { return "Base"; }
 
// data
int mA;
};
 
struct CDerived : public CBase {
CDerived( void ) { std::cout << "Consruct Derived\n"; }
  ~CDerived( void ) { std::cout << "Destruct Derived\n"; }
 
   virtual const char * Name( void ) { return "Derived"; }
 
// data
int mB;
};
 
template <class T>
struct CWrapper {
CWrapper( void )
{
mData = malloc(sizeof(T));
new (mData) T;
}
 
~CWrapper( void )  { Destroy(); }
 
T * data( void ) { return (T *) mData; }
const T * data( void ) const { return (T *) mData; }
 
template <class T2>
void Init( void )
{
Destroy();
mData = malloc(sizeof(T2));
new (mData) T2;
}
 
virtual void Destroy( void )
{
if (mData) {
delete (T *) mData;
mData = 0;
}
}
 
template <class T2>
CWrapper & operator = ( const CWrapper<T2> & src )
{
Destroy();
mData = malloc(sizeof(T2));
new (mData) T2(*src.data());
return *this;
}
 
private:
 
// data
void * mData;
};
 
int main(int argc, char *argv[])
{
CWrapper<CBase> wrap1;
CWrapper<CDerived> wrap2;
 
std::cout << "\nTest1\n\n";
std::cout << "wrap1 = " << wrap1.data()->Name() << " wrap2 = " << wrap2.data()->Name() << std::endl;
wrap1 = wrap2;
std::cout << "wrap1 = " << wrap1.data()->Name() << " wrap2 = " << wrap2.data()->Name() << std::endl;  
 
std::cout << "\nTest2\n\n";
CWrapper<CBase> wrap3;
std::cout << "wrap3 = " << wrap3.data()->Name() << std::endl;  
wrap3.Init<CDerived>();
std::cout << "wrap3 = " << wrap3.data()->Name() << std::endl;  
 
return 0;
}
Правильно ли я понял? Спасибо
 
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #6 : Июль 13, 2016, 11:25 »

Нет, не дотягивается он до не-виртуального деструктора. Это конечно можно пережить но
Код
C++ (Qt)
Ну хрен с ней, давай психическую...
(классика советского кино)

Код
C++ (Qt)
#include <iostream>
 
//------------------------ CDeepPointer ---------------------------------
 
template <class T>
struct CDeepPointer {
struct CControl;
struct CData;
 
template<class T2>
struct CDataStorage;
 
template<class T2>
struct CTypedControl;
 
CDeepPointer( void )
{
CTypedControl<T>().create(mData, 0);
}
 
~CDeepPointer( void ) { reset(); }
 
T * data( void ) { return (T *) mData.mPtr; }
const T * data( void ) const { return (T *) mData.mPtr; }
bool isNull( void ) const { return !mData.mPtr; }
T * operator -> ( void ) { return data(); }
 
template <class T2>
void set( const T2 * src = 0 )
{
reset();
CTypedControl<T2>().create(mData, src);
}
 
void reset( void )
{
if (mData.mCtl)
mData.mCtl->destroy(mData);
}
 
CDeepPointer & operator = ( const CDeepPointer<T> & src )
{
reset();
if (src.mData.mCtl)
src.mData.mCtl->create(mData, src.mData.mPtr);
return *this;
}
 
template <class T2>
CDeepPointer & operator = ( const CDeepPointer<T2> & src )
{
reset();
if (src.mCtl)
src.mCtl->create(mData, src.mData.mPtr);
return *this;
}
 
struct CData {
void * mPtr;
CControl * mCtl;
};
 
template<class T2>
struct CDataStorage {
CDataStorage( void ) {}
CDataStorage( const T2 * src ) : mData(*src) {}
 
// data members
T2 mData;
CTypedControl<T2> mCtl;
};
 
struct CControl {
virtual void create( CData & data, const void * src ) const = 0;
virtual void destroy( CData & data) const = 0;
};
 
template <class T2>
struct CTypedControl : public CControl {
virtual void create( CData & data, const void * src ) const
{
CDataStorage<T2> * storage = 0;
if (src)
storage = new CDataStorage<T2>((T2 *) src);
else
storage = new CDataStorage<T2>;
 
data.mPtr = storage;
data.mCtl = &storage->mCtl;
}
 
virtual void destroy( CData & data ) const
{
delete (CDataStorage<T2> *) data.mPtr;
data.mPtr = 0;
data.mCtl = 0;
}
};
 
private:
CData mData;
};
 
//------------------------ Test Classes -----------------------------
 
struct CBase {
CBase( void )  { std::cout << "Consruct Base\n"; }
~CBase( void ) { std::cout << "Destruct Base\n"; }
 
   virtual const char * Name( void ) { return "Base"; }
 
// data
int mA;
};
 
struct CDerived : public CBase {
CDerived( void )  { std::cout << "Consruct Derived\n"; }
~CDerived( void ) { std::cout << "Destruct Derived\n"; }
 
   virtual const char * Name( void ) { return "Derived"; }
 
// data
int mB;
};
 
//------------------------ main -----------------------------
 
int main(int argc, char *argv[])
{
CDeepPointer<CBase> ptr1, ptr2;
ptr2.set<CDerived>();
 
std::cout << "\nTest1\n\n";
std::cout << "ptr1 = " << ptr1->Name() << " ptr2 = " << ptr2->Name() << std::endl;
ptr1 = ptr2;
std::cout << "ptr1 = " << ptr1->Name() << " ptr2 = " << ptr2->Name() << std::endl;
 
std::cout << "\nTest2\n\n";
ptr1.reset();
 
return 0;
}
 
Edit: почистил и исправил ошибку
« Последнее редактирование: Июль 13, 2016, 15:56 от Igors » Записан
_Bers
Бывалый
*****
Offline Offline

Сообщений: 486


Просмотр профиля
« Ответ #7 : Июль 13, 2016, 12:02 »

классика советского кино

говнокод лютый, но идею вы вроде уловили.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #8 : Июль 13, 2016, 15:56 »

// при помощи паттерна type erasure
// враппер "запомнил" тип ресурса, который он захватил
К чему сводится это "запоминание"? Не явно же его запоминать как число или строку. Я понял так

- внутри враппера объявляется базовый класс с виртуальными методами, от него наследуется темплейт класс

- в момент когда известен/приходит реальный тип (напр set<T2> выше) создается экземпляр реального типа и указатель на него сохраняется в враппере как указатель на базовый класс. Он и используется (через виртуалы) для вызова нужных методов, реальный тип по-прежнему врапперу неизвестен, но он может рулить.

Верно?

говнокод лютый, но идею вы вроде уловили.
Там неверно было, исправил. По поводу говнокода - интересная тема. Возможно через месяц-другой я сам не пойму текст что сейчас написал. Куча каких-то мелких классов, хелперов, сходу хрен поймешь что они делают. Ну а как иначе? Я не могу их не писать связавшись с этой гребаной "магией". В конце-концов исходники стандартного std никак не более понятны. Нет, конечно на этапе использования все хорошо, когда пристроился и знаешь как юзать. Но все-таки наверное незатейливый вариант с виртуальным Clone предпочтительнее (хотя объективно слабее). Что Вы об этом думаете?
Записан
_Bers
Бывалый
*****
Offline Offline

Сообщений: 486


Просмотр профиля
« Ответ #9 : Июль 13, 2016, 16:39 »

Верно?
ага.

Там неверно было, исправил. По поводу говнокода - интересная тема. Возможно через месяц-другой я сам не пойму текст что сейчас написал. Куча каких-то мелких классов, хелперов, сходу хрен поймешь что они делают. Ну а как иначе?


1.
православный хелпер:
не аллоцируется в куче.
это медленно,
и в этом нет ни какой нужды.

он не должен иметь собственных детей.

2.
наследника тоже без нужды не нужно аллоцировать в куче.
обычно выделяют стековую память, и алллоцируют в куче,
только если наследник совсем туда не влезает.

3.
локальные классы сильно усложняют понимание.
можно вынести в спейс details.
код сразу упроститься.

4.
не нужно использовать в плюсовом коде сишные подходы.

5.
у вас там были какие то баги.
не знаю, что вы потом поправили,
я сильно не вникал.



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

поскольку использовал стек, а не дин. аллокации,
то особое внимание уделил выравниванию данных,
и стрикт-алиасингу.

ну и потом просто наслаждался жизнью.

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

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

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

Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #10 : Июль 13, 2016, 18:55 »

Вот переделал по образцу QSharedPtr, пусть клиент заряжает new - проще. Попинайте
Код
C++ (Qt)
#include <iostream>
#include <string>
#include <sstream>
 
//------------------------ CDeepPointer ---------------------------------
 
template <class T>
struct CDeepPointer {
struct CControl;
 
template<class T2>
struct CTypedControl;
 
CDeepPointer( void ) :
mPtr(0),
mCtl(0)
{
}
 
template<class T2>
CDeepPointer( T2 * src ) :
mPtr(src),
mCtl(0)
{
if (mPtr)
mCtl = new (mPlace) CTypedControl<T2>();
}
 
~CDeepPointer( void ) { reset(); }
 
T * data( void ) { return (T *) mPtr; }
const T * data ( void ) const { return (T *) mPtr; }
bool isNull ( void ) const { return !mPtr; }
T * operator -> ( void ) { return data(); }
T & operator *  ( void ) { return *data(); }
bool operator ! ( void ) const  { return isNull(); }
operator bool ( void ) const  { return !isNull(); }
 
template <class T2>
void reset( T2 * src )
{
if (data() == TypeCheck(src)) return;
reset();
mPtr = src;
mCtl = new (mPlace) CTypedControl<T2>();
}
 
void reset( void )
{
if (mCtl) {
mCtl->destroy(mPtr);
mPtr = 0;
mCtl = 0;
}
}
 
CDeepPointer & operator = ( const CDeepPointer & src )
{
return operator = <T>(src);
}
 
template <class T2>
CDeepPointer & operator = ( const CDeepPointer<T2> & src )
{
if (data() == TypeCheck(src.data())) return *this;
reset();
if (src.control())
mPtr = src.control()->create(src.data(), mPlace);
return *this;
}
 
CControl * control( void ) const
{
return mCtl;
}
 
template<class T2>
const T * TypeCheck( const T2 * src )
{
return src;
}
 
struct CControl {
virtual void * create( const void * src, void * place ) const = 0;
virtual void destroy( void * data) const = 0;
};
 
template <class T2>
struct CTypedControl : public CControl {
virtual void * create( const void * src, void * place ) const
{
new (place) CTypedControl<T2>;
return src ? new T2(*(T2 *) src) : new T2;
}
 
virtual void destroy( void * data ) const
{
delete (T2 *) data;
}
};
 
private:
void * mPtr;
CControl * mCtl;
char mPlace[sizeof(CTypedControl<T>) * 2];
};
 
//------------------------ Test Classes -----------------------------
 
struct CBase {
CBase( int a = 0 ) : mA(a) { std::cout << "Consruct Base\n"; }
 
// Note: destructor is non-virtualintentionally, for test purposes
~CBase( void ) { std::cout << "Destruct Base\n"; }
 
   virtual std::string toString( void )
{
std::stringstream strm;
strm << "Base(" << mA << ")";
return strm.str();
}
 
// data
int mA;
};
 
struct CDerived : public CBase {
CDerived( int a = 0, int b = 0 ) : CBase(a), mB(b) { std::cout << "Consruct Derived\n"; }
~CDerived( void ) { std::cout << "Destruct Derived\n"; }
 
   virtual std::string toString( void )
{
std::stringstream strm;
strm << "Derived(" << mA << "," << mB << ")";
return strm.str();
}
 
// data
int mB;
};
 
//------------------------ main -----------------------------
 
int main( int argc, char *argv[] )
{
CDeepPointer<CBase> ptr1(new CBase(1));
CDeepPointer<CBase> ptr2(new CDerived(2, 3));
 
std::cout << "\nTest1\n\n";
std::cout << "ptr1 = " << ptr1->toString() << " ptr2 = " << ptr2->toString() << std::endl;
ptr1 = ptr2;
std::cout << "ptr1 = " << ptr1->toString() << " ptr2 = " << ptr2->toString() << std::endl;
 
std::cout << "\nTest2\n\n";
ptr2.reset();
 
return 0;
}
 
Edit: нашел еще ошибку - исправил
« Последнее редактирование: Июль 14, 2016, 12:30 от Igors » Записан
_Bers
Бывалый
*****
Offline Offline

Сообщений: 486


Просмотр профиля
« Ответ #11 : Июль 14, 2016, 11:56 »

Вот переделал по образцу QSharedPtr, пусть клиент заряжает new - проще. Попинайте

в код не вникал, но что-то там у вас не срослось:
http://rextester.com/YEPJT26606

Цитировать
Error(s):
Process exit code is not 0: -1073740940
Consruct Base
Consruct Base
Consruct Derived

Test1

ptr1 = Base(1) ptr2 = Derived(2,3)
ptr1 = Derived(2,3) ptr2 = Derived(2,3)


зы:
вот я вам выше предложил написать годный тестовый проект
(например, с использованием google mock)

и провести полноценное тестирование.

судя по всему, вы решили пренебречь этим.

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

конечно, с позиции человека,
который уже специалист по части шаблоно-магии, и тп,
тут никаких особых сложностей нет.

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

поэтому, вы окажете услугу всем своим коллегам,
если тщательно протестируете свой механизм,
и избавите их от необходимости вникать в детали его реализации.
Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #12 : Июль 14, 2016, 12:35 »

вот я вам выше предложил написать годный тестовый проект
(например, с использованием google mock)
А давайте. Вот я зарегистрипрвался и поместил туда свой код http://rextester.com/LWYA1042 Что мне делать дальше на пути к "полноценному тестированию"? (просто никогда не пользовался google mock). Спасибо
Записан
_Bers
Бывалый
*****
Offline Offline

Сообщений: 486


Просмотр профиля
« Ответ #13 : Июль 14, 2016, 23:19 »

вот я вам выше предложил написать годный тестовый проект
(например, с использованием google mock)
А давайте. Вот я зарегистрипрвался и поместил туда свой код http://rextester.com/LWYA1042 Что мне делать дальше на пути к "полноценному тестированию"? (просто никогда не пользовался google mock). Спасибо

так я вам помочь не могу.
для тестирования я использую фреймворк google test

но его нет на онлайн компиляторе

впрочем, если вас это заинтересовало,
то вот пример использования:
https://www.youtube.com/watch?v=6pp8S56sS2Y&amp;feature=youtu.be

хотя я конечно немножко по другому делаю.
но в целом для старта неплохой мануал.

Записан
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 11445


Просмотр профиля
« Ответ #14 : Июль 15, 2016, 10:38 »

Ой нет, это сложно. И так почти 2 дня провозился с "интересной фишкой", а работа стоит. Немного расширил свое понимание template - ну и хорошо. Спасибо за помощь.
Записан
Страниц: [1] 2   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.178 секунд. Запросов: 22.