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

Я хотел бы определить во время компиляции, если указатель на Полученный может быть брошен от указателя для Базирования без dynamic_cast <>. Является это возможное использование шаблонами и метапрограммированием? Это не точно та же проблема как определение, если Основой является виртуальный базовый класс Полученных, потому что Основой мог быть суперкласс виртуального базового класса Полученных.

Спасибо, Tim Update: Я чувствовал себя хорошо об этом методе:

#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 () прием?

22
задан Tim 24 May 2010 в 07:05
поделиться

7 ответов

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

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

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

11
ответ дан 29 November 2019 в 05:40
поделиться

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

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

2
ответ дан 29 November 2019 в 05:40
поделиться

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

-2
ответ дан 29 November 2019 в 05:40
поделиться

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

1
ответ дан 29 November 2019 в 05:40
поделиться

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

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 это сделал. Если у вас есть класс Base и класс Derived, то имеет место следующее.

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.

5
ответ дан 29 November 2019 в 05:40
поделиться

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

0
ответ дан 29 November 2019 в 05:40
поделиться

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

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

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

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

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

sizeof(*((T*)0))

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

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

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

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

3
ответ дан 29 November 2019 в 05:40
поделиться
Другие вопросы по тегам:

Похожие вопросы: