C ++: можно ли обнаружить виртуальное наследование во время компиляции? - PullRequest
22 голосов
/ 24 мая 2010

Я хотел бы определить во время компиляции, может ли указатель на Derived быть приведен из указателя на Base без dynamic_cast <>. Возможно ли это с помощью шаблонов и метапрограммирования? Это не совсем та же проблема, что и определение того, является ли Base виртуальным базовым классом Derived, поскольку Base может быть суперклассом виртуального базового класса Derived.

Спасибо, Тим Обновить: Я чувствовал себя хорошо об этом методе:

#include <iostream>

using namespace std;

class Foo
{
};

class Bar : public Foo
{
};

class Baz : public virtual Foo
{
};

class Autre : public virtual Bar
{
};

typedef char Small;
class Big { char dummy[2]; };

template<typename B, typename D>
struct is_static_castable
{
    const B* foo;
    char bar[1];
    static Small test(char(*)[sizeof(static_cast<const D*>(foo)) == sizeof(const D*)]);
    static Big test(...);
    enum { value = (sizeof(test(&bar)) == sizeof(Small)) };
};

int main()
{

    cout << "Foo -> Bar: " << is_static_castable<Foo, Bar>::value << "\n";
    cout << "Foo -> Baz: " << is_static_castable<Foo, Baz>::value << "\n";
    cout << "Foo -> Autre: " << is_static_castable<Foo, Autre>::value << "\n";
}

Но с gcc это не работает:

multi-fun.cpp: In instantiation of ‘is_static_castable<Foo, Baz>’:
multi-fun.cpp:38:   instantiated from here
multi-fun.cpp:29: error: cannot convert from base ‘Foo’ to derived type ‘Baz’ via virtual base ‘Foo’
multi-fun.cpp:29: error: array bound is not an integer constant
multi-fun.cpp: In instantiation of ‘is_static_castable<Foo, Autre>’:
multi-fun.cpp:39:   instantiated from here
multi-fun.cpp:29: error: cannot convert from base ‘Foo’ to derived type ‘Autre’ via virtual base ‘Bar’
multi-fun.cpp:29: error: array bound is not an integer constant

Я не понимаю, что можно сделать с помощью трюка sizeof ()?

Ответы [ 8 ]

11 голосов
/ 06 июня 2010

У меня была такая же проблема, однажды. К сожалению, я не совсем уверен насчет виртуальной проблемы. Но: Boost имеет класс с именем is_base_of (см. здесь ), который позволит вам выполнить что-либо. как следующее

BOOST_STATIC_ASSERT((boost::is_base_of<Foo, Bar>::value));

Кроме того, в Boost's type_traits есть класс is_virtual_base_of, возможно, это то, что вы ищете.

5 голосов
/ 29 июля 2010

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

class A 
{};

class B : virtual public A
{};

class C : public A
{};

// Default template which will resolve for 
// all classes
template 
< typename T
, typename Enable = void 
>
struct FooTraits
{
    static void foo(){
        std::cout << "normal" << std::endl;
    }
};

// Specialized template which will resolve
// for all sub classes of A
template 
< typename T 
>
struct FooTraits 
    < T
    , typename boost::enable_if
         < boost::is_virtual_base_of< A, T>
         >::type
    >
{
    static void foo(){
        std::cout << "virtual base of A" << std::endl;
    }
};

int main(int argc, const char * argv[] ){
    FooTraits<C>::foo(); // prints "normal"
    FooTraits<B>::foo(); // prints "virtual base of A"
}

и если вы хотите знать, как Boost это сделал. Если у вас есть класс База и класс Производные, тогда справедливо следующее.

struct X : Derived, virtual Base 
{
   X();
   X(const X&);
   X& operator=(const X&);
   ~X()throw();
};

struct Y : Derived 
{
   Y();
   Y(const Y&);
   Y& operator=(const Y&);
   ~Y()throw();
};

bool is_virtual_base_of = (sizeof(X)==sizeof(Y)));

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

3 голосов
/ 05 августа 2010

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

Во-вторых, трюк sizeof должен работать с приведениями 0, а не с фактическими указателями или объектами - это гарантирует нулевые накладные расходы, а также то, что он не будет выполняться, пока вы не сделаете это правильно.

В-третьих, вам нужно объявить 2 шаблонных класса или структуры, один из которых является производным только от D, а другой - от D и виртуального B, а затем привести 0 к их указателям, разыменовать их и затем sizeof.

4-й - Есть ли у вас какая-либо серьезная причина для того, чтобы пытаться быть политически корректным с использованием static_cast вместо прямой трансляции здесь? Компилятор всегда будет выводить из того, что вы ищете больше нытья, и в этом случае вы определенно нет.

Кстати, вам не нужно захватывать полный код у Александреску - просто захватите основную технику, которая в основном просто:

sizeof(*((T*)0))

Александреску действительно хорош в уборке после уловки.

Да, и помните, что компилятор не должен оценивать размер аргументов или создавать неиспользуемые шаблонные классы и структуры - поэтому, если это так, то это ошибка компилятора, и если вы заставляете его это делать, то это ваша ошибка: -)

Как только вы это получите, вам нужно точно и в положительных терминах определить, что ваше утверждение «если указатель на Derived может быть приведен из указателя на Base без dynamic_cast <>», фактически означает в терминах отношений класса - просто сказать « без оператора / функции Q "не делает проблему хорошо определенной, и вы не можете решить то, что не можете определить - честно: -)

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

2 голосов
/ 28 мая 2010

Вы пробовали SUPERSUBCLASS от Loki?

http://loki -lib.sourceforge.net /

1 голос
/ 15 мая 2014

Существует шаблон взлома, чтобы сделать это во время компиляции.

Сначала вам нужно создать класс интерфейса, подобный этому:

template <typename T>
class SomeInterface
{
public:
    inline int getSomething() const;
};

template<typename T>
inline int SomeInterface<T>::getSomething() const
{
    return static_cast<T const*>(this)->T::getSomething();
}

Идея такова: Приведение this к T и вызов метода с тем же именем и теми же аргументами из него. Как вы можете видеть, функция-оболочка встроена, поэтому во время выполнения не будет никаких издержек производительности или стека вызовов.

А затем создайте классы, реализующие интерфейс следующим образом:

class SomeClass : public SomeInterface<SomeClass>
{
    friend class SomeInterface<SomeClass>;

public:
    int getSomething() const;
};

Затем просто добавьте реализации производных методов как обычно.

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

1 голос
/ 23 июля 2010

После преобразования в базовый указатель вы можете получить только ошибку времени выполнения (dynamic_cast). Вы можете определить методы, используя шаблонные параметры, и получить ошибку компиляции, используя специализации шаблонов.

0 голосов
/ 07 августа 2010

Если вы хотите знать во время компиляции, вы можете взять производный класс в качестве параметра но если единственное, что у вас есть, это Base, то вы не можете знать, относится ли оно к какому-либо из классов foo, bar и т. д. Эта проверка может быть выполнена только тогда, когда указатель преобразуется в базу. Я думаю, что в этом вся цель dynamic_cast <>

0 голосов
/ 06 июня 2010

Это может быть немного наивно (я гораздо сильнее в C, чем в C ++), поэтому я не могу понять, что вы пытаетесь сделать, но если вы используете указатели, о которых вы говорите, в стиле C приведение типов работает отлично (например, (D *)foo) или эквивалентный C ++ reinterpret_cast. При этом, это может быть очень опасно, потому что у вас нет никакой проверки во время выполнения, и, следовательно, необходимо быть уверенным, что вы приводите в правильный тип. Опять же, если вы хотите иметь простой способ проверить правильность этого предположения или нет, мы вернемся к исходной точке. Однако, похоже, вы пытаетесь сравнить указатели выше, которые все одинаковы (в основном это целые числа). Насколько я знаю, нет способа определить класс объекта во время выполнения в C ++, включая sizeof, который работает во время компиляции. По сути, нет способа сделать то, что вы хотите сделать (по крайней мере, не со стандартным C ++), однако фактическое приведение не вызовет каких-либо проблем, просто неправильно используя указатель нового преобразования. Если вам абсолютно необходима эта функциональность, вам, вероятно, лучше включить виртуальную функцию в ваш базовый класс, которая сообщает, что это за класс (предпочтительно со значением enum), и перегрузить ее в любом подклассе, который вы надеетесь определить, можете ли вы также выполнять приведение. .

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