【问题标题】:Why is my "this" pointer inside a member function null?为什么我的“this”指针在成员函数中为空?
【发布时间】:2019-03-10 22:50:44
【问题描述】:

我不想给你看一本书,所以我要简化一下。 我有一个名为“Tile”的类,看起来类似于:

class Tile{
public:
   struct Type{ unsigned int uvIndex; ... };
   Tile(Tile::Type* tt){ tt_ = tt; ... }
   Type tt_ = nullptr;
   Tile* getNorthEast(){
       printf("this %p\n", this); //for debugging purposes
       /* calculation; "this" pointer is need for that  */
       return northEastTilePtr;
   }
};

我想将它们中的许多分配到一个永远不会再次移动的大数组中(不需要 a )所以我手动进行分配

//Manual allocation to prevent constructor from being called
Tile* tiles = (Tile*)malloc(numTiles*sizeof(Tile));
if(tiles == nullptr) throw std::bad_alloc();

由于最初不知道 Tile 类型,我不能调用 new 来执行此操作。 现在我分配了内存,但没有调用构造函数。 世界生成器运行并调用每个图块

tiles[tileIndex] = Tile(tileTypePtr);

现在它应该已经创建了所有类型的所有对象。 如果我渲染场景并且没有调用getNorthEast();,我可以看到,由于uvIndex(它只是指定要渲染纹理的哪个部分),类型已正确设置。所以 tt_ 设置正确,构造函数也必须正确运行。 然而!如果我现在调用getNorthEast();,printf 语句告诉我this 指针是00000000。这会打乱计算并导致崩溃。

这可能是由于未定义的行为,但我看不出我做错了什么......我需要你的帮助。

编辑 1: 所以我现在看了一下新的安置。 我已将分配和分配更改为:

//allocation 
tiles = (Tile*)new char[numTiles*sizeof(Tile)];
//assignment
new (&tiles[tileIndex]) Tile(tileTypePtr);

但是this 仍然是一个空指针。这次分配应该是完全覆盖,没有未定义的行为。分配的作用与std::vector.reserve()基本相同;

附加信息:getNorthEast() 在主循环中的调用要晚得多。现在只是初始化。因此,它应该不会影响对象是否被构造。

编辑 2: 我刚刚重新尝试使用向量。同样的结果。

std::vector<Tile> tiles;
//allocation
tiles_.reserve(numTiles);
//construction for each index
tiles.emplace_back(Tile(tileTypePtr)); 

由于即使您输入了向量,也没有手动操作,我开始怀疑原因是我没有提到的一些原因。我会继续寻找,直到找到其他可能会引起麻烦的东西。

【问题讨论】:

  • malloc 函数只分配内存,它不调用构造函数! 以任何方式使用未构造的对象(包括分配给它们,因为调用对象赋值运算符) 导致undefined behavior。请get a few good books to read 了解如何避免此类问题。
  • tiles[tileIndex] = Tile(tileTypePtr); 是错误的,因为tiles[tileIndex] 使用了未初始化的对象。您必须先调用构造函数,然后才能分配给它。
  • @Someprogrammerdude "现在我分配了内存,但没有调用构造函数。" 我很确定 OP 已经知道了。
  • //Manual allocation to prevent constructor from being called:为什么?这很奇怪。你应该给出解释。是的,我们需要minimal reproducible example。我们至少需要调用getNorthEast的代码
  • 我想将它们中的许多分配到一个永远不会再移动的大数组中(不需要 a )所以我手动进行分配 -- 你知道吗?大多数(如果不是所有)std::vector 的实现都做同样的事情,但要正确使用placement-new。所以你在重新发明轮子,只是你的重新发明被打破了。

标签: c++ class pointers null undefined-behavior


【解决方案1】:
tiles[tileIndex] = Tile(tileTypePtr);

它的作用是创建一个临时的Tile 实例,然后(移动)将该临时分配给(应该)存在于数组中的Tile 实例。但是那个Tile 实例不存在,所以程序的行为是不确定的。

这是将对象构造到预先存在的内存缓冲区中的正确方法:

 // you can use malloc, but I see no reason to need it
 unsigned char *buffer = new unsigned char[numTiles*sizeof(Tile)];
 auto offset = sizeof(Tile) * tileIndex;
 Tile* tptr = new(buffer + offset) Tile(tileTypePtr);

这种为新对象重用内存的语法称为“新放置”。您需要包含 &lt;new&gt; 标头才能使用它。

但是,没有必要自己重新实现这个缓冲区重用。 std::vector&lt;Tile&gt; 为您做这件事,除了处理危险的内存管理以及异常安全和指针别名规则的微妙之处。您已经遇到了一个警告。没有必要跳到下一个。 std::vector 通常是非默认可构造类型的动态数组的理想解决方案。


编辑:下面的答案基于我最初的解释,即您打算在数组中包含不同类型的对象。我现在注意到您的对象中有一个 Type 指针,并认为您实际上可能只存储具有不同内部 Type 指针的 Tile 实例(我假设 tt_ 应该是一个指针)。

但是,您的结构似乎有点不稳定。你有没有考虑过数组的用户是如何知道你在哪个索引中构造了什么类型的Tile?您是否考虑过数组中的所有对象都必须具有相同的大小和对齐要求?编译器不能强制执行这些考虑。

如果您事先知道可能的图块类型列表,我建议您使用:

// let Tile1, Tile2, Tile3 be the possible types
std::vector<std::variant<Tile1,Tile2,Tile3>>

如果你不能限制类型列表,那么你可以使用:

std::vector<std::any>

【讨论】:

  • 好的,我已经尝试了新的展示位置。我还将它添加到编辑中以显示它的外观。本质上应该是一样的。然而this 仍然是一个空指针。最初我使用向量(reserve() 用于分配,emplace_back() 用于构造),但由于出现了这个问题,我试图降低一点,以便更好地了解正在发生的事情。
  • 您对 tt_ 的假设是正确的。索引是从指针减法中得知的。事实上:这就是“this”指针的用途。 unsigned int tileIndex = (unsigned int)(this - tiles)。这在我使用 C 时有效(我将程序移至 C++ 以获得更好的功能)。我应该补充一点,tiles 变量是全局的,所以它可以被 Tile 函数访问
  • @RIJIK 我很确定问题中的新代码违反了指针别名规则,因此违反了 UB。也就是说,我猜 this-null 问题可能是由程序中其他地方的其他 UB 引起的。
  • @ user2079303 您可能是正确的,它是由其他原因引起的。 “指针别名规则”是什么意思,我的代码有什么问题?试图发现任何潜在的问题。
  • @RIJIK 这是错误的:(Tile*)new char[numTiles*sizeof(Tile)]Tile 指针不允许给 char 对象起别名。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-04-06
  • 2014-10-18
  • 1970-01-01
  • 2010-10-02
  • 2011-10-16
相关资源
最近更新 更多