成员变量和对象切片
Member variables and object slicing
This answer 建议可以通过使用指针来克服向量中的对象切片。根据我的测试,在处理基础和派生 classes 之间共享的变量时,情况确实如此。例如,给定这些 classes:
class Base {
public:
int x = 1;
};
class Derived : public Base {
public:
Derived() : Base() {
x = 2;
}
int y;
};
可见Derived在构造时重新定义了x为2。当放入 Base class 的向量中时,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;
有没有办法在保留派生 classes 独有的成员变量的同时绕过这个问题,如果没有,是否有更好的方法将对象存储在有序容器中?
编辑
之前编辑的相关内容已移至我下面的回答中。在找到使用智能指针的方法(或确保我的原始指针实现没有内存问题)之前,我不打算接受答案。
Is there a way to bypass this issue while preserving member variables unique to derivative classes, and if not is there a superior way to store objects in an ordered container?
有很多技巧可以实现类似的效果。所有这些都已在 SO 的不同答案中提到。
一份简短的(可能不完整的)简历:
双重调度。完全基于多态性,基本思想是将访问者 class 传递给存储在容器中的实例,它们通过重载方法提升自己,将派生类型传回给访问者。
类型擦除。您可以通过多种方式做到这一点。例如,您可以将派生的 classes 作为指向 void 或基 class 的指针以及指向接收擦除类型的函数的指针来存储。指向函数的指针应该是函数模板的特化地址,它能够将 class 静态转换回它的原始类型并使用它执行任何任务。
标记联盟。您不直接存储派生的 classes。相反,您将它们包装在一个专用结构中,该结构携带足够的信息以通过一组标记(即匿名枚举)将它们转换回派生类型。
std::variant
。从 C++17 开始,您可以使用 std::variant
,这是一种类型安全的标记联合,您可以通过专门的访问者访问它。
AOB(任何其他业务)。
我不认为重复 SO 上已有的示例是值得的。您可以使用上面的关键字搜索它们并找到您要查找的内容。
每种技术都有其优点和缺点。其中一些允许您在调用者的上下文中提取原始类型,因此您可以在需要时将对象传递给其他函数。其他一些完全擦除类型并为您提供 handlers 以对原始类型执行操作,但您永远不会在调用者的上下文中取回它。由于所使用语言的特点和他们所吸引的演员,这些差异通常会影响性能(以一种您几乎无法观察到的方式)。
改变 classes
Base
和 Derived
的 class 定义应更改为:
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!
按预期运行并输出 255
和 65535
。
This answer 建议可以通过使用指针来克服向量中的对象切片。根据我的测试,在处理基础和派生 classes 之间共享的变量时,情况确实如此。例如,给定这些 classes:
class Base {
public:
int x = 1;
};
class Derived : public Base {
public:
Derived() : Base() {
x = 2;
}
int y;
};
可见Derived在构造时重新定义了x为2。当放入 Base class 的向量中时,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;
有没有办法在保留派生 classes 独有的成员变量的同时绕过这个问题,如果没有,是否有更好的方法将对象存储在有序容器中?
编辑
之前编辑的相关内容已移至我下面的回答中。在找到使用智能指针的方法(或确保我的原始指针实现没有内存问题)之前,我不打算接受答案。
Is there a way to bypass this issue while preserving member variables unique to derivative classes, and if not is there a superior way to store objects in an ordered container?
有很多技巧可以实现类似的效果。所有这些都已在 SO 的不同答案中提到。
一份简短的(可能不完整的)简历:
双重调度。完全基于多态性,基本思想是将访问者 class 传递给存储在容器中的实例,它们通过重载方法提升自己,将派生类型传回给访问者。
类型擦除。您可以通过多种方式做到这一点。例如,您可以将派生的 classes 作为指向 void 或基 class 的指针以及指向接收擦除类型的函数的指针来存储。指向函数的指针应该是函数模板的特化地址,它能够将 class 静态转换回它的原始类型并使用它执行任何任务。
标记联盟。您不直接存储派生的 classes。相反,您将它们包装在一个专用结构中,该结构携带足够的信息以通过一组标记(即匿名枚举)将它们转换回派生类型。
std::variant
。从 C++17 开始,您可以使用std::variant
,这是一种类型安全的标记联合,您可以通过专门的访问者访问它。AOB(任何其他业务)。
我不认为重复 SO 上已有的示例是值得的。您可以使用上面的关键字搜索它们并找到您要查找的内容。
每种技术都有其优点和缺点。其中一些允许您在调用者的上下文中提取原始类型,因此您可以在需要时将对象传递给其他函数。其他一些完全擦除类型并为您提供 handlers 以对原始类型执行操作,但您永远不会在调用者的上下文中取回它。由于所使用语言的特点和他们所吸引的演员,这些差异通常会影响性能(以一种您几乎无法观察到的方式)。
改变 classes
Base
和 Derived
的 class 定义应更改为:
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!
按预期运行并输出 255
和 65535
。