Подтвердить что ты не робот

С++: Может ли виртуальное наследование быть обнаружено во время компиляции?

Я хотел бы определить во время компиляции, если указатель на Derived можно отличить от указателя на базу без 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()?

4b9b3361

Ответ 1

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

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

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

С уважением,

mefiX

Ответ 2

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

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"
}

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

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

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

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

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

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

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

sizeof(*((T*)0))

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

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

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

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

Ответ 5

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

Ответ 6

В момент компиляции есть шаблонный взлом.

Сначала вам нужно создать класс интерфейса следующим образом:

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;
};

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

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

Ответ 7

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

Ответ 8

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