Почему при выводе значения я получаю значения мусора?

Я пытаюсь узнать об идиоме PIMPL и C++ в целом.

У меня есть класс с интерфейсом в стиле PIMPL, который устанавливает значение int равным 7. Но я получаю мусорное значение при печати, и я не понимаю, почему.

Код

test.cpp

#include <iostream>
#include "Test.h"
struct Foo::Bar
{
    int value;
};

Foo::Foo()
{
    Bar tempBar;
    myBar = &tempBar;
    myBar->value = 7;
}
void Foo::printValue()
{
    std::cout << "Value = " << myBar->value << std::endl;
}
int main()
{
    Foo myFoo;
    myFoo.printValue();
    return 0;
}

test.h

class Foo
{
    private:
        struct Bar;
        Bar* myBar;
    public:
        Foo();
        void printValue();
        //~Foo();
};

Выход

Значение = 2147120498

4 ответа

Решение

mybar указатель на локальную переменную внутри Foo конструктор. Когда конструктор выходит, переменная исчезает, но myBar все еще указывает на старую память.

Поскольку вы хотите реализовать PIMPL, на самом деле есть только один выбор. Вам нужно динамически распределять myBar с помощью newи освободить его delete в Foo деструктор. Вам также нужно будет добавить конструктор копирования и оператор присваивания копии Foo также, чтобы избежать утечки памяти:

test.cpp

Foo::Foo()
{
    myBar = new Bar;
    myBar->value = 7;
}

Foo::Foo(const Foo &src)
{
    myBar = new Bar;
    *myBar = *(src.myBar);
}

Foo::~Foo()
{
    delete myBar;
}

Foo& Foo::operator=(const Foo &rhs)
{
    *myBar = *(rhs.myBar);
    return *this;
}

test.h

class Foo
{
private:
    struct Bar;
    Bar* myBar;
public:
    Foo();
    Foo(const Foo &src);
    ~Foo();

    void printValue();

    Foo& operator=(const Foo &rhs);
};

Если бы вы не реализовывали PIMPL, тогда был бы другой вариант. Делать myBar быть не указателем члена Foo класс вместо:

test.cpp

Foo::Foo()
{
    myBar.value = 7;
}

void Foo::printValue()
{
    std::cout << "Value = " << myBar.value << std::endl;
}

test.h

class Foo
{
private:
    struct Bar
    {
        int value;
    };

    Bar myBar;

public:
    Foo();
    void printValue();
};

Используя современный C++, вы можете сделать pimpl следующим образом:

#include <memory>

class Foo
{
    struct Bar;
    std::unique_ptr<Bar> myBar;
public:
    Foo();
    ~Foo();
    void printValue();
};    

unique_ptr является одним из немногих стандартных контейнеров библиотеки, который работает с неполными типами. Преимущество такого подхода заключается в том, что если вы допустите ошибку, например, закомментируете деструктор или попытаетесь скопировать Foo тогда компилятор поймает это за вас.

Тела в файле.cpp могут быть:

Foo::Foo(): myBar( new Bar ) { myBar->value = 7; }
Foo::~Foo() {}

tempBar выталкивается из стека в конце конструктора, так как это локальная переменная, поэтому вы используете неопределенное поведение.

Возможно, вы хотите использовать new а также delete в деструкторе.

Вы храните временный адрес. Вместо этого вам нужно выделить его:

Foo::Foo()
{
    myBar = new Bar;
    myBar->value = 7;
}

И тогда вы должны следовать правилу трех, предоставляя деструктор и конструктор копирования.

Другие вопросы по тегам