成员变量和对象切片

时间:2017-07-11 04:06:26

标签: c++ inheritance object-slicing

This answer表明可以使用指针克服向量中的对象切片。从我的测试来看,在处理基类和派生类之间共享的变量时都是如此。例如,给定这些类:

class Base {
public:
     int x = 1;
};

class Derived : public Base {
public:
    Derived() : Base() { 
        x = 2;
    }
    int y;
};

可以看出,Derived在构造时将x重新定义为2。当放入Base类的向量时,x变量的行为与预期的一样。

vector<unique_ptr<Base>> baseVec;               // Vector of pointers to Base objects
unique_ptr<Derived> derivedPtr(new Derived());  // Pointer to Derived object
baseVec.push_back(std::move(derivedPtr));       // Add pointer with std::move()
std::cout << baseVec[0]->x << std::endl;        // Outputs 2

但是,尝试使用仅属于Derived的y变量会导致错误C2039: 'y' is not a member of 'Base',如下所示:

std::cout << baseVec[0]->y << std::endl;

有没有办法绕过这个问题,同时保留派生类特有的成员变量,如果没有,是否有更好的方法在有序容器中存储对象?

修改

上一个编辑的相关内容已移至我的回答中。在我找到使用智能指针的方法(或确保我的原始指针实现没有内存问题)之前,我不打算接受答案。

2 个答案:

答案 0 :(得分:0)

  

有没有办法绕过这个问题,同时保留派生类特有的成员变量,如果没有,是否有更好的方法在有序容器中存储对象?

有许多技术可以实现类似的功能。所有这些都已在SO的不同答案中提到过 一份简短的(可能是不完整的)简历:

  • 双重调度。完全基于多态性,基本思想是将访问者类传递给容器中存储的实例,并通过重载方法提升自己将派生类型传递给访问者。

  • 类型擦除。你可以用几种方式做到这一点。例如,您可以将派生类存储为void或基类的指针以及指向接收已擦除类型的函数的指针。指向函数的指针应该是函数模板的特化的地址,该函数模板能够静态地将类转换回它的原始类型并用它执行任何任务。

  • 标记的联合。您不能直接存储派生类。而是将它们包装在一个专用的结构中,该结构带有足够的信息,通过一组标记(即匿名枚举)将它们强制转换为派生类型。

  • std::variant。从C ++ 17开始,您可以使用std::variant,这是一种类型安全的标记联合,您可以通过专用访问者访问。

  • AOB(任何其他业务)。

我不认为重复SO上已有的例子是值得的。您可以使用上面的关键字搜索它们并获得您想要的内容 每种技术都有它的优点和缺点。其中一些允许您在调用者的上下文中提取原始类型,因此您可以根据需要将对象传递给其他函数。其他一些人完全擦除了类型,并为您提供了处理程序来对原始类型执行操作,但您永远不会在调用者的上下文中将其恢复。这些差异通常会影响表现(以一种难以观察的方式),因为正在使用的语言的特征和他们吸引的演员。

答案 1 :(得分:0)

更改课程

BaseDerived的类定义应更改为:

class Base {
public:
    int x = 1;

    virtual void f() { }
};

class Derived : public Base {
public:
    Derived() : Base() { x = 2; }
    int y = 55;

    void f() { }
};

值得注意的是虚拟函数m(),允许dynamic_cast处理指针的转换。

使用unique_ptr

唯一指针可用于存储,检索,修改和安全删除指向Derived对象的指针向量中的Base对象的指针。下面的示例使用堆分配的对象。

/* Insertion */
// Create a vector of unique_ptr to Base and add a unique_ptr to Derived to it
std::vector<std::unique_ptr<Base>> v;
std::unique_ptr<Derived> p1(new Derived());
v.push_back(std::move(p1));

/* Retrieval */
// Release the pointer at base to place it into p2
std::unique_ptr<Derived> p2(dynamic_cast<Derived*>(v[0].release()));

/* Modification */
p2->x = 0xff;    // Modify x (Decimal 255)
p2->y = 0xffff;  // Modify y (Decimal 65535)
int y = p2->y;   // Copy the value of the Derived object's y to an int for use later

// Move the pointer to the modified object back to v
v[0] = std::move(p2); 

/* Output */
std::cout << v[0]->x << std::endl; // Outputs 255
std::cout << y << std::endl;       // OUtputs 65535

/* Delete */
// To safely delete the object, it must be treated as a pointer to a Derived object
// Thus it must be released from the control of v and deleted via p3, which is cast in the same manner as p2 was
std::unique_ptr<Derived> p3(dynamic_cast<Derived*>(v[0].release()));
p3.reset(); // This deletes the object as well, not just the reference to it!

按预期运作,输出25565535