【问题标题】: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; 获取的指针以供以后使用。你的向量已经不复存在了。 (提示十位评论者指出“从不”有点强烈,我的回答是“有时可以,但这并不意味着你应该”。)
  • 我很好奇您所说的“与学生一起工作”是什么意思。
  • 你应该习惯于实现构造函数的初始化列表(不要与std::initialiser_list混淆!):Pair() : previous("a"), next("b") { };您避免默认初始化+分配,而是通过参数直接初始化。此外,某些类型(引用、非默认构造类型)只能以这种方式初始化。

标签: c++ pointers vector


【解决方案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;pairsdangling 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; <>

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2016-03-28
      • 1970-01-01
      • 1970-01-01
      • 2020-06-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多