菊花链函数调用指针

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。有趣的是,将Gamevector<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 构造函数本地向量的向量指针吗? 您永远不应该存储您使用&amp; 获取的指针以供以后使用。你的向量已经不复存在了。 (提示十位评论者指出“从不”有点强烈,我的回答是“有时可以,但这并不意味着你应该”。) 我很好奇您所说的“与学生一起工作”是什么意思。 关于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

名为pairsvector 仅在构造函数运行时存在。您存储了指向 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 上,但它必须使用 newheap 上分配缓冲区。 然后当vectorGame::Game() 末尾超出范围时,它会deletes 这个缓冲区。 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-&gt;pairs 是dangling pointer。这可以通过直接在构造函数中分配this-&gt;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&lt;std::vector&lt;Pair&gt;&gt;std::shared_ptr&lt;std::vector&lt;Pair&gt;&gt;。还请考虑摆脱using namespace std; <>。

【讨论】:

以上是关于菊花链函数调用指针的主要内容,如果未能解决你的问题,请参考以下文章

this指针 (保存调用成员函数对象的地址)

如何调用成员函数指针?

C 语言指针间接赋值 ( 指针作为 函数参数 的意义 | 间接赋值 代码示例 )

ARM Cortex M3上的GCC:从特定地址调用函数

菊花链 Python/Django 自定义装饰器

类中函数指针调用函数