LINUX.ORG.RU

Создание «общих» функций-обёрток для одноимённых методов отнаследованных классов

 , , ,


0

2

ЛОР, есть нубовопросы.

Есть библиотека на C++, в ней есть некий базовый класс Base и несколько отнаследованных от него (пусть будут Derived1, Derived2 и т.д.). Также для этой библиотеки есть обёртка для C, которая сейчас выглядит примерно так:

void *Derived1_Create() {
    Derived1 *der = new Derived1();
    return static_cast<void*>(der);
}
...

double Derived1_DoSomething(void *v) {
    Derived1 *der = static_cast<Derived1*>(v);
    return der->DoSomething();
}
...

void Derived1_Destroy(void *v) {
    Derived1 *der = static_cast<Derived1*>(v);
    delete der;
}
1) Можно ли определять общие функции для методов вида DerivedN::DoSomething? То есть, можно ли не писать для каждого класса Derived такую функцию в обёртке, а создать одну, в которой происходит каст к базовому классу (в котором метод DoSomething тоже существует (но реализация не обязательно такая же, просто он принимает такие же аргументы и возвращает значение того же типа))?
double Base_DoSomething(void *v) {
    Base *bas = static_cast<Base*>(v);
    return bas->DoSomething();
}
Это работает, но какие могут быть подводные камни?

2) То же самое, но про деструкторы. Если мы будем удалять объекты указанным ниже способом, чем это чревато?

void Base_Destroy(void *v) {
    Base *bas = static_cast<Base*>(v);
    delete bas;
}

...
int main() {
    derived1 = Derived1_Create();
    derived42 = Derived42_Create();
    Base_Destroy(derived1);
    Base_Destroy(derived42);
}

1) #define? 2) Я не спец по крестам, но если деструктор виртуальный, то всё должно быть в порядке. В конце концов, почему бы тебе самому это не протестировать при помощи printf/iostream? Я же правильно понял, что речь о (не)вызове деструктора?

Deleted ()

1. Небольшие накладные расходы на вызов виртуальных функций. Если программа далает чуть больше, чем просто бесконечно их вызывает, ты этого даже и не заметишь.

2. Отсутствием виртуального деструктора у Base и последующими утечками памяти.

staseg ★★★★★ ()

Как по мне это достаточно частый юзкейз библиотко-писателей. Может еже есть готовый генератор сишной оболочки? Или это идея для стартапа?

staseg ★★★★★ ()
Ответ на: комментарий от staseg

1) Вызывает много и часто, но пока что не почувствовал. Просто посмотрю, как сильно падает производительность.

2) Понял, что читать, спасибо.

Ceiling_QB ★★★★ ()
Ответ на: комментарий от Deleted

1) Создавать функции с помощью макросов? Суть в том, что я это буду вызывать из скриптовых языков, и там будет удобно, если вместо кучи функций будет одна, в которую можно передавать любого наследника Base.

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

Ceiling_QB ★★★★ ()
Последнее исправление: Ceiling_QB (всего исправлений: 2)
Ответ на: комментарий от Ceiling_QB

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

Deleted ()

Что-то такое на коленках соорудил.

// test.h

#ifndef TEST_H
#define TEST_H


/*
 * Foo
 */

typedef struct Foo *FooRef;

FooRef Foo_new();
void Foo_destroy(FooRef self);
void Foo_doSomething(FooRef self);

/*
 * Bar
 */

typedef struct Bar *BarRef;

BarRef Bar_new();


#endif
// test.cpp

#include <iostream>

using std::cout;

/*
 * Foo
 */

struct Foo {
    Foo() {cout << "Foo()\n";}
    virtual ~Foo() {cout << "~Foo()\n";}
    virtual void doSomething() {cout << "Foo::doSomething()\n";}
};

extern "C" Foo *Foo_new() {
    return new Foo;
}

extern "C" void Foo_destroy(Foo *self) {
    delete self;
}

extern "C" void Foo_doSomething(Foo *self) {
    self->doSomething();
}

/*
 * Bar
 */

struct Bar: Foo {
    Bar() {cout << "Bar()\n";}
    virtual ~Bar() {cout << "~Bar()\n";}
    virtual void doSomething() {cout << "Bar::doSomething()\n";}
};

extern "C" Bar *Bar_new() {
    return new Bar;
}
#include "test.h"

int main() {
    FooRef foo = Foo_new();
    Foo_doSomething(foo);
    Foo_destroy(foo);

    BarRef bar = Bar_new();
    Foo_doSomething((FooRef)bar);
    Foo_destroy(foo);
}

Выхлоп:

Foo()
Foo::doSomething()
~Foo()
Foo()
Bar()
Bar::doSomething()
~Bar()
~Foo()

Deleted ()

Кстати, C11 есть возможность использовать? Можно будет обойтись без виртуальных методов, если использовать _Generic оттуда и макросы.

Deleted ()
Ответ на: комментарий от Ceiling_QB

1) Вызывает много и часто, но пока что не почувствовал. Просто посмотрю, как сильно падает производительность.

Если у вас код переопределённых функций задан не в заголовочных файлах, то код замедлится на пару быстрых инструкций, что незначительно по сравнению с двумя вызовами функций (обертка и плюсовая функция). Если переопределенные функции - в заголовочных файлах, то в зависимости от параметров оптимизации компилятора может сильнее замедлиться (на 2 инструкции + один вызов функции), т.е. на вызов может быть потрачено на 50% больше времени, но это тоже может быть мало по сравнению со временем полезной работы функции.

Sorcerer ★★★★★ ()

1) Никаких. Это стандартное применение наследования. Уж оно должно работать.

2) Да. Только деструктор виртуальным сделай, на тот случай если унаследованным от Base нужно будет за собой «убирать».

a1batross ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.