菊花链函数调用指针
Posted
技术标签:
【中文标题】菊花链函数调用指针【英文标题】:Daisy-chaining function calls on pointers 【发布时间】:2019-03-29 12:03:30 【问题描述】:我最近在与学生一起工作时遇到了一个场景,我很难理解为什么以下示例会失败。
我有一个指向对象Game
的指针,而Game
本身也有一个指向vector<Pair>
的指针。失败的行是main()
的最后一行,我是菊花链方法:
gamePointer->getPairs()->push_back(pair);
在上述行中,getPairs()
返回一个vector<Pair>*
,然后调用push_back()
将一个新的Pair
添加到向量中。这导致read access violation
。有趣的是,将Game
的vector<Pair>
替换为string
,例如,允许我编写以下内容,并且它有效:
gamePointer->getPairs()->append("B");
我已经简化了问题并复制了一个完整的例子:
#include "pch.h"
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Pair
private:
string previous;
string next;
public:
Pair();
Pair(string previous, string next);
string getPrevious();
string getNext();
void setPrevious(string previous);
void setNext(string next);
;
class Game
private:
vector<Pair>* pairs;
public:
Game();
vector<Pair>* getPairs();
void setPairs(vector<Pair>* pairs);
;
Pair::Pair()
this->setPrevious("a");
this->setNext("b");
Pair::Pair(string previous, string next)
this->setPrevious(previous);
this->setNext(next);
string Pair::getPrevious()
return this->previous;
string Pair::getNext()
return this->next;
void Pair::setPrevious(string previous)
this->previous = previous;
void Pair::setNext(string next)
this->next = next;
Game::Game()
vector<Pair> pairs;
pairs.reserve(10);
this->setPairs(&pairs);
vector<Pair>* Game::getPairs()
return this->pairs;
void Game::setPairs(vector<Pair>* pairs)
this->pairs = pairs;
int main()
Game game;
Game* gamePointer = &game;
Pair pair("Previous", "Next");
gamePointer->getPairs()->push_back(pair);
【问题讨论】:
你不是在尝试存储,然后访问指向 Game 构造函数本地向量的向量指针吗? 您永远不应该存储您使用&
获取的指针以供以后使用。你的向量已经不复存在了。 (提示十位评论者指出“从不”有点强烈,我的回答是“有时可以,但这并不意味着你应该”。)
我很好奇您所说的“与学生一起工作”是什么意思。
关于using namespace std...
你应该习惯于实现构造函数的初始化列表(不要与std::initialiser_list
混淆!):Pair() : previous("a"), next("b")
;您避免默认初始化+分配,而是通过参数直接初始化。此外,某些类型(引用、非默认构造类型)只能以这种方式初始化。
【参考方案1】:
Game::Game()
vector<Pair> pairs; // DANGER!
pairs.reserve(10);
this->setPairs(&pairs); // ARGHH!
// < pairs dies on this line
名为pairs
的vector
仅在构造函数运行时存在。您存储了指向 this 的指针,但指向的对象立即超出范围!
相反,只需将成员设为 vector
而不是指针:
class Game
private:
vector<Pair> pairs; // the vector itself is a member of Game
然后你可以像这样创建getPairs
:
vector<Pair>* Game::getPairs() // return a pointer
return &pairs;
或者这个:
vector<Pair>& Game::getPairs() // return a reference
return pairs;
你目前正在做的是Undefined Behaviour - 这意味着你的程序是非法的,任何事情都可能发生,包括看起来正常工作。
当您将vector
替换为string
时,您会看到“看起来正常工作” - 您的代码仍然损坏,只是您没有注意到!
我可以对为什么会发生这种情况做出有根据的猜测,但这绝不能保证。
vector
行为:
vector
对象本身在 stack 上,但它必须使用 new
在 heap 上分配缓冲区。
然后当vector
在Game::Game()
末尾超出范围时,它会delete
s 这个缓冲区。
vector
对象本身不再有效,但内存恰好在您下次尝试使用它之前没有被覆盖。
您尝试使用(不再存在的)vector
,但内存仍然恰好包含指向缓冲区的指针。缓冲区已被释放,因此您在尝试访问它时会遇到“读取访问冲突”。
string
行为:
string
确实没有有一个分配缓冲区。这是std::string
的有效实现,因为它使用“小字符串优化”,其中小字符串(例如,最多 16 个字符)直接存储在 string
对象本身内,而不是在分配的缓冲区。
因此,string
,包括实际内容,都在堆栈上。
string
对象在Game::Game()
末尾超出范围,但在您下次尝试使用它之前,内存恰好没有被覆盖。
您尝试使用(不再存在的)string
,但内存仍然恰好包含有效的“短字符串”魔法。
因为这是在stack上,而不是在heap上,内存实际上并没有被释放。因此,尝试访问它不会导致“读取访问冲突”。
但它仍然是完全非法的!
【讨论】:
【参考方案2】:看Game
的构造函数:
Game::Game()
vector<Pair> pairs;
pairs.reserve(10);
this->setPairs(&pairs);
pairs
是一个局部变量。它将在构造函数结束时被销毁。含义this->pairs
是dangling pointer。这可以通过直接在构造函数中分配this->pairs
来解决:
Game::Game()
this->pairs = new vector<Pair>;
this->pairs->reserve(10);
如果你这样做,总是提供一个解构器来清理分配的数据:
Game::~Game()
delete this->pairs;
这可以让您摆脱setPairs
。最后这个类应该是这样的:
class Game
private:
vector<Pair>* pairs;
public:
Game();
~Game();
vector<Pair>* getPairs();
;
老实说,我不太喜欢您将 vector
之类的类和原始指针混合在一起的方式。在这个简单的示例中,您甚至根本不需要指针。请参阅BoBTFish answer,了解如何进行无指针实现。但是,如果您绝对有义务使用指针,请改用std::unique_ptr<std::vector<Pair>>
或std::shared_ptr<std::vector<Pair>>
。还请考虑摆脱using namespace std;
<>。
【讨论】:
以上是关于菊花链函数调用指针的主要内容,如果未能解决你的问题,请参考以下文章