1

如果它是安全的,我很感兴趣,在某些条件下将基类的实例向下转换(感谢迈克)到派生类我认为样本是最简单的解释方法:

struct BaseA
{
    void foo() const {}     
    double bar_member;
    // no virtuals here
};

struct DerivedA : public BaseA
{
    double bar(double z) {bar_member = z;return bar_member;}
    // DerivedA does not add ANY member variables to BaseA.
    // It also does not introduce ANY virtual functions.
};    

struct BaseB
{
    BaseA baseA;
};

// add extra functionality to B, to do this,
// i also need more functionality on baseA.
struct DerivedB : public BaseB
{
    // is this "safe"? since BaseA and DerivedA
    // should have the same memory layout?!?
    DerivedA& getA() {return *static_cast<DerivedA*>(&baseA);}

    double foo(double z) {return getA().bar(z);} 
};

#include <iostream>

int main(int argc, char** argv)
{
    DerivedB b;
    // compiles and prints expected result
    std::cout << b.foo(argc) << std::endl;
}

在我的例子中,类 BaseA 和 BaseB 实现了某种视图概念。但是,它们还包含在派生类中添加更多功能所需的所有数据成员。我知道我可以实现视图以仅保存对提供功能的类的引用。但是,这会带来一些缺点:

  • 我需要重写视图类的整个界面。
  • 在我的例子中,派生类拥有一个额外的模板参数(回调类型),我想在视图中删除它。因此,视图不能直接引用提供功能的类。

我测试了我的代码,它可以工作,但是,我并不真正相信这种方法。是的,我知道我可以通过虚拟等实现其中的一些功能,但它确实对性能至关重要......

欢迎任何想法,提示

马丁

对于感兴趣的人:我通过以下方式更改了我的设计:

struct DerivedB : public BaseB
{
    // encapsule the required extended functionality of BaseA member
    struct OperateOnBaseA
    {
         OperateOnBaseA(BaseA& a);
         double dosomething(double);
    };

    OperateOnBaseA a_extension;

    DerivedB() :a_extension(baseA) {}

    double foo(double z) {return a_extension.dosomething();} 
}; 
4

3 回答 3

1

至于技术方面:2011标准,5.2.9.11,静态转换当然是禁止的。设 B 为 D 的底:

如果“指向 cv1 B 的指针”类型的纯右值指向实际上是 D 类型对象的子对象的 B,则生成的指针指向 D 类型的封闭对象。否则,强制转换的结果是未定义的。

另一方面,如果有人能找到不只是这样做的实现,我会感到惊讶,因为类、方法和静态强制转换的明显实现。

于 2014-03-18T11:17:25.433 回答
0

对于您要尝试做的事情,我发现您的方法不够干净。假设有一个“数据源类型”SourceA和一个“数据视图类型” ViewB,我会更像这样:

#include <iostream>

using namespace std;

template<typename T>
class SourceA_base
{
protected:
    T data;
public:
    using value_type = T;
    SourceA_base(T&& a) : data(std::move(a)) { }
    SourceA_base(T const& a) : data() { }
    void foo() const {}
};

template<typename T>
class SourceA : public SourceA_base<T>
{
    using B = SourceA_base<T>;
public:
    using B::B;
    T bar(T z) { return B::data = z; }
};

template<typename U>
class ViewB_base
{
protected:
    U&& source;
public:
    using value_type = typename std::remove_reference<U>::type::value_type;
    ViewB_base(U&& a) : source(std::forward<U>(a)) { }
};

template<typename U>
class ViewB : public ViewB_base<U>
{
    using B = ViewB_base<U>;
    using T = typename B::value_type;
public:
    using B::B;
    T foo(T z) { return B::source.bar(z); }
};

int main ()
{
    using S = SourceA<double>;
    S a{3.14};
    ViewB<S&> b{a};
    std::cout << b.foo(6.28) << std::endl; // compiles and prints expected result
    std::cout << ViewB<S>{S{2}}.foo(4) << std::endl; // still works
}

也就是说,所有(源/视图)类型都是模板化的,视图包含引用,并且没有向下转换。关于您对使用参考的保留:

  • 重写整个界面:现在不需要了,感谢模板。
  • 擦除回调类型:首先,类型擦除和性能关键的应用程序并不总是好朋友。其次,你最好让回调擦除它自己的底层类型,而不是视图擦除回调的类型。每个班级都应该做自己的工作。或者,不要删除类型并使它们成为模板参数。

我使用了右值引用,以便整个事情也适用于临时对象,如我的第二个示例所示。也许构造函数在这里并不总是完整/正确的。例如const参考;我实际上会有完全模板化的构造函数(接受通用引用),但是要使它与一个参数隐式定义的复制/移动构造函数合作有点棘手(需要类型特征和enable_if),我只想在这里强调这个想法。

您还可以考虑使用元组来保存数据,利用它们的空基优化。

至于你原来的问题,这种沮丧是我永远不会做的;对于技术方面,请参阅 Peter Schneider 的回答。

于 2014-03-18T11:17:44.960 回答
0

如其他答案所述,您现有的代码具有未定义的行为。如果您不介意一些真正可怕的代码,您可以通过销毁对象并在同一位置baseA创建对象来避免这种情况,因此向下转换是有效的:DerivedA

#include <new>

struct DerivedB : public BaseB
{
  DerivedB()
  {
    static_assert( sizeof(BaseA) == sizeof(DerivedA), "same size" );
    baseA.~BaseA();
    ::new(&baseA) DerivedA();
  }
  ~DerivedB()
  {
    getA().~DerivedA();
    ::new(&baseA) BaseA();
  }

    DerivedA& getA() {return *static_cast<DerivedA*>(&baseA);}

    double foo(double z) {return getA().bar(z);} 
};

析构函数恢复原始类型的对象,因此当BaseB析构函数销毁其baseA成员时,它会在对象上运行正确类型的析构函数。

但我会避免这样做并重新设计你的类以另一种方式解决它。

于 2014-03-18T11:44:06.240 回答