带有继承和强制转换的段错误



我使用的是现有的C++,看起来像这样

class Geometry {
public:
enum {BOX, MESH} type;
std::string name;
};
class Mesh : public Geometry {
public:
std::string filename;
};
std::shared_ptr<Geometry> getGeometry() {
std::shared_ptr<Geometry> geom = std::make_shared<Geometry>();
geom->name = "geometry";
Mesh *mesh = new Mesh();
mesh->name = "name";
mesh->filename = "filename";
mesh->type = Geometry::MESH;
geom.reset(mesh);
return geom;
}

我需要扩展这些类提供的功能,所以我派生了这两个类

#include <memory>
#include <iostream>
class Geometry {
public:
enum {BOX, MESH} type;
std::string name;
};
class Mesh : public Geometry {
public:
std::string filename;
};
std::shared_ptr<Geometry> getGeometry() {
std::shared_ptr<Geometry> geom = std::make_shared<Geometry>();
geom->name = "geometry";
Mesh *mesh = new Mesh();
mesh->name = "name";
mesh->filename = "filename";
mesh->type = Geometry::MESH;
geom.reset(mesh);
return geom;
}
class GeometryDerived : public Geometry {
public:
void consumeGeometry() {
Geometry geom = static_cast<Geometry>(*this);
std::cout << geom.name << std::endl;
if(geom.type == Geometry::MESH) {
Mesh *mesh = static_cast<Mesh*>(&geom);
std::cout << mesh->filename << std::endl;
// mesh->get_output();
}
}
};
class MeshDerived : public Mesh {
public:
std::string get_output() {
return this->filename;        
}
};

int main(int argc, char** argv)
{
std::shared_ptr<Geometry> geom = getGeometry(); // this object is returned by the library
std::shared_ptr<GeometryDerived> geomDerived = std::static_pointer_cast<GeometryDerived>(geom);
geomDerived->consumeGeometry();
return 0;
}

segfault一致(在我实际的代码,其垃圾字符串)

std::cout << mesh->filename << std::endl;

,但其他ints和doubles打印正确。甚至geom.name也打印正确。这里的弦是怎么回事?什么是正确的方式来投射对象在这里?两次强制转换对象看起来很难看。下面是GDB的输出

GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./cpp-example...
(gdb) run
Starting program: /home/user/cpp-example
name
Program received signal SIGSEGV, Segmentation fault.
__memmove_avx_unaligned_erms () at ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:383
383 ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S: No such file or directory.
(gdb) bt
#0  __memmove_avx_unaligned_erms () at ../sysdeps/x86_64/multiarch/memmove-vec-unaligned-erms.S:383
#1  0x00007ffff7c247b2 in _IO_new_file_xsputn (n=140737488346512, data=0xdb8bbc853a3b3400, f=<optimized out>) at fileops.c:1236
#2  _IO_new_file_xsputn (f=0x7ffff7d7e6a0 <_IO_2_1_stdout_>, data=0xdb8bbc853a3b3400, n=140737488346512) at fileops.c:1197
#3  0x00007ffff7c18541 in __GI__IO_fwrite (buf=0xdb8bbc853a3b3400, size=1, count=140737488346512, fp=0x7ffff7d7e6a0 <_IO_2_1_stdout_>) at libioP.h:948
#4  0x00007ffff7ed2824 in std::basic_ostream<char, std::char_traits<char> >& std::__ostream_insert<char, std::char_traits<char> >(std::basic_ostream<char, std::char_traits<char> >&, char const*, long) ()
from /lib/x86_64-linux-gnu/libstdc++.so.6
#5  0x0000555555556858 in GeometryDerived::consumeGeometry (this=0x55555556def0) at ../downcasting.cpp:23
#6  0x0000555555556513 in main (argc=1, argv=0x7fffffffdec8) at ../downcasting.cpp:53

编辑1:下面的更改可以正常工作,但这不是我想要的

int main(int argc, char** argv)
{
std::shared_ptr<Geometry> geom = getGeometry();
if(geom->type == Geometry::MESH) {
std::shared_ptr<MeshDerived> meshDerived = std::static_pointer_cast<MeshDerived>(geom);
std::cout << meshDerived->filename << std::endl;
}
// std::shared_ptr<GeometryDerived> geomDerived = std::static_pointer_cast<GeometryDerived>(geom);
// geomDerived->consumeGeometry();
return 0;
}

你的代码有多个问题:

  • GeometryDerived::consumeGeometry中,Geometry geom = static_cast<Geometry>(*this);通过切片当前对象创建了一个Geometry类型的新对象。稍后在该函数中执行Mesh *mesh = static_cast<Mesh*>(&geom);,它返回一个无效指针(因为geom不是Mesh)。通过该指针访问数据是UB。(由于大多数编译器使用的内存布局,它可能适用于大多数实现中在Geometry中声明的数据成员;没有定义良好的行为。)

  • static_pointer_cast不检查指向对象是否确实是请求的类型。即使对象是不同的类型,强制转换也会成功,但是返回的指针无效。getGeometry函数返回一个指向Mesh对象的指针,该对象与GeometryDerived无关,因此强制转换返回一个无效指针。


Edit 1的注释部分。不,这一行也是错误的:

std::shared_ptr<MeshDerived> meshDerived = std::static_pointer_cast<MeshDerived>(geom);

原因和我上面提到的一样:getGeometry返回一个指向Mesh的指针,而不是MeshDerived。这段代码可能会在大多数编译器上工作,因为MeshDerivedMesh具有相同的内存布局,但这仍然不是一个有效的c++。

看起来你想要"添加方法"到运行时中的现有类。这在c++中是不可能的。要么你必须在主类(例如Mesh)中定义你需要的方法,要么使用访问者模式,即在一个单独的类中定义新功能,通过Mesh等的公共成员对其进行操作。