2015-12-19 1 views
1

По причинам стирания типа у меня есть шаблон A<T>, который может содержать любой тип данных. Когда A имеет полиморфный тип Derived, который является производным от Base и я бросил его в A<Base>, неопределенное поведение ССЗ дезинфицирующее сообщения об ошибках во время выполнения:неопределенное поведение при шаблоне литья, имеющем полиморфный тип

#include <iostream> 

struct I 
{ 
    virtual ~I() = default; 
}; 

template<typename T> 
struct A : public I 
{ 
    explicit A(T&& value) : value(std::move(value)) {} 
    T& get() { return value; } 
private: 
    T value; 
}; 

struct Base 
{ 
    virtual ~Base() = default; 
    virtual void fun() 
    { 
     std::cout << "Derived" << std::endl; 
    } 
}; 

struct Derived : Base 
{ 
    void fun() override 
    { 
     std::cout << "Derived" << std::endl; 
    } 
}; 

int main() 
{ 
    I* a_holding_derived = new A<Derived>(Derived()); 
    A<Base>* a_base = static_cast<A<Base>*>(a_holding_derived); 
    Base& b = a_base->get(); 
    b.fun(); 
    return 0; 
} 

компилировать & кнопкуВыполнения

$ g++ -fsanitize=undefined -g -std=c++11 -O0 -fno-omit-frame-pointer && ./a.out 

выход:

main.cpp:37:62: runtime error: downcast of address 0x000001902c20 which does not point to an object of type 'A' 

0x000001902c20: note: object is of type 'A<Derived>' 

00 00 00 00 20 1e 40 00 00 00 00 00 40 1e 40 00 00 00 00 00 00 00 00 00 00 00 00 00 21 00 00 00 

       ^~~~~~~~~~~~~~~~~~~~~~~ 

       vptr for 'A<Derived>' 

    #0 0x400e96 in main /tmp/1450529422.93451/main.cpp:37 

    #1 0x7f35cb1a176c in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2176c) 

    #2 0x400be8 (/tmp/1450529422.93451/a.out+0x400be8) 


main.cpp:38:27: runtime error: member call on address 0x000001902c20 which does not point to an object of type 'A' 

0x000001902c20: note: object is of type 'A<Derived>' 

00 00 00 00 20 1e 40 00 00 00 00 00 40 1e 40 00 00 00 00 00 00 00 00 00 00 00 00 00 21 00 00 00 

       ^~~~~~~~~~~~~~~~~~~~~~~ 

       vptr for 'A<Derived>' 

    #0 0x400f5b in main /tmp/1450529422.93451/main.cpp:38 

    #1 0x7f35cb1a176c in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2176c) 

    #2 0x400be8 (/tmp/1450529422.93451/a.out+0x400be8) 


Derived 

live example on coliru

У меня есть два вопроса:

  1. выводится дезинфицирующее Правильна?
  2. Если да, то как будет действующее преобразование от A<Derived> до A<Base>?
+1

Статический приведение от 'I *' к 'X' имеет неопределенное поведение, поскольку он фактически указывает на подобъект' Y', а 'X' и' Y' - разные типы (соответственно 'A ' и 'A '). –

+0

Это не то, что люди обычно называют «стиранием типа». У стирания типа будет один (не шаблонный) тип 'A', который вы могли бы * построить * из произвольных типов. Полиморфизм будет в частных частях. Примерами стирания типа в стандартной библиотеке являются 'std :: function' и' std :: experimental :: any'. –

+1

@KerrekSB У меня есть другой тип, который обертывается вокруг 'A'; Я не показывал его здесь, чтобы привести пример в соответствие с минимальным. –

ответ

4

Вопрос заключается в том, что A<Base> и A<Derived> не имеют никакого отношения друг к другу на всех. Они могут быть представлены совершенно по-другому. Для броска, который вы пытаетесь сделать, необходимо, чтобы A<Base> был базовым классом A<Derived>, что явно не так.

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

3

Я не уверен, о ваших целях дизайна, но поставить обсуждение в перспективе, вот типичный пример типа стирания: Один класса Foo, который предоставляет уничтоженный вызов bar:

#include <memory> 
#include <type_traits> 
#include <utility> 

class Foo 
{ 
    struct ImplBase 
    { 
     virtual ~ImplBase() = default; 
     virtual int bar(int, int) = 0; // This line is the whole point! 
    }; 

    std::unique_ptr<ImplBase> impl; 

    template <typename T> struct Impl : ImplBase 
    { 
     Impl(T t) t_(std::move(t)) {} 

     int bar(int a, int b) override { return t_.bar(a, b); } 

     T t_; 
    }; 

public: 
    template <typename T> 
    Foo(T && x) 
    : impl(new Impl<typename std::decay<T>::type>(std::forward<T>(x))) 
    {} 

    int bar(int a, int b) // Not virtual! Foo is a "value-like" class. 
    { 
     return impl->bar(a, b); 
    } 
}; 

Утилита в таком подходе заключается в том, что теперь вы можете иметь один тип интерфейса, который использует Foo, и вы можете назвать этот интерфейс любым типом, который соответствует требованиям Impl (который вы, конечно же, без ссылки на детали реализации).

Для примера рассмотрим следующую функцию:

void DoSomething(Foo a, int x, int y) 
{ 
    UpdateCounter(a.bar(x, y)); 
} 

Эта функция может быть определена и скомпилированный в одном ЕП, никогда не прикоснулся снова.Но будущие пользователи, которые никогда, возможно, были в причинном контакте с DoSomething автором, могут передавать произвольные объекты, которые выставляют bar функцию:

struct X { double bar(long int, int); }; 
struct Y { char bar(int, float, bool = false); }; 

DoSomething(Foo(X{}), 10, 20); 
DoSomething(Foo(Y{}), 20, 10); 

Примечания:

  • Тип стирание обеспечивает одноранговые полиморфизм.
  • Требования к типам клиентов являются структурными, а не связанными с наследованием. Подумайте, «утка» или «концепция».
  • Тип стираемой конструкции предоставляет функциональность, а не иерархическую связанность.
  • Вы можете сделать Foo скопированным, если требуется, чтобы Impl был скопирован (что соответствует требованию на T).
  • Мы используем raw new; там нет поддержки распределителя. Поддержка выделения типа стирания оказалась очень сложной, особенно если состояние стираемого типа предполагается скопировать.
Смежные вопросы