【问题标题】:What strategy should be used when trying to point to automatic objects?尝试指向自动对象时应该使用什么策略?
【发布时间】:2019-06-30 08:57:09
【问题描述】:

我正在开发一个相对较大的库(约 13000 行)作为个人实用程序库。它专门使用 STL 容器和智能指针进行内存管理——但现在我发现自己处于一种情况,由于在 STL 中明显缺乏明显的解决方案,我可以看到使用普通指针。不过,我想保持现代 C++ 风格。

这是我的mcve

Struct Foo - 一个 int-wrapper 结构,显示调用了哪些成员函数。

struct Foo {
    Foo(int x) {
        this->x = x;
        std::cout << "constructor\n";
    }
    Foo(const Foo& other) {
        this->x = other.x;
        std::cout << "copy constructor\n";
    }
    ~Foo() {
        std::cout << "destructor\n";
    }

    int x;
};

看着main我创建了一个struct Foo的自动实例:

int main() {
    Foo foo{ 0 };
    std::cout << "\nfoo.x : " << foo.x << "\n\n";    
}

输出 >

constructor

foo.x : 0

destructor

就这么简单。 - 现在,如果我想指向foo 并操纵其内容。


std::unique_ptrstd::shared_ptr 这样的智能指针不会达到这种效果(显然!)。使用std::make_unique&lt;T&gt;()(/std::make_shared&lt;T&gt;())会动态分配内存,只复制值:

Foo foo{ 0 };
std::unique_ptr<Foo> ptr = std::make_unique<Foo>(foo);

ptr->x = 2;

std::cout << "\nptr->x : " << ptr->x << '\n';
std::cout << "foo.x  : " << foo.x << "\n\n";

输出 >

constructor
copy constructor

ptr->x : 2
foo.x  : 0 // didn't change  

destructor
destructor

所以这行不通。但是,他们的void reset(pointer _Ptr = pointer()) noexcept 成员函数允许直接分配一个指针:

std::unique_ptr<Foo> ptr;
Foo foo{ 0 };

ptr.reset(&foo);

ptr->x = 2;

std::cout << "\nptr->x : " << ptr->x << '\n';
std::cout << "foo.x  : " << foo.x << "\n\n";

输出 >

constructor

ptr->x : 2
foo.x  : 2

destructor
destructor //crash

但是崩溃! foo get 正常解构,而std::shared_ptr also 想要解构其已经解构的解引用值。

HEAP[main.exe]: 指定给 RtlValidateHeap(...) 的地址无效


使用 const 指针:

Foo foo{ 0 };
Foo* const ptr = &foo;

ptr->x = 2;

std::cout << "\nptr->x : " << ptr->x << '\n';
std::cout << "foo.x  : " << foo.x << "\n\n";

输出 >

constructor

ptr->x : 2
foo.x  : 2

deconstructor
  • 1 次分配,1 次解除分配。
  • 禁止复制。
  • 实际上是在操纵值。

到目前为止,指针感觉是最好的选择。


我只是在寻找一种指向自动分配对象的方法。到目前为止,如果不回到简单的指针,这似乎非常困难。

我自己提供了一种解决方案——使用/*const*/ std::reference_wrapper&lt;T&gt;。 (请随时纠正我有关此解决方案的问题)

但我不确定什么是最好的策略。指针? std::reference_wrapper 也许?我在使用std::shared_ptr / std::unique_ptr 时犯了错误吗?

有没有更好、更直接的 STL 类?

【问题讨论】:

  • 为什么你想要指针语义?出于什么目的?你实际上并没有告诉我们你想要做什么。
  • 您也可以使用自定义删除器吗?将一些对象分配在堆栈上而将其他对象分配在堆上也很危险!
  • 为什么你认为这是糟糕的设计?
  • 您尚未完全指定您的目标,但据我所知,它与那里接受的答案中描述的用例相匹配。人们在不需要时经常使用动态分配,但这并不意味着您应该不惜一切代价避免它。
  • 它也不过是一个带有花哨名称的原始指针,真的

标签: c++ c++11 pointers memory-management reference


【解决方案1】:

因为你的目标还不清楚,所以我在这个上稍微对冲一下。


如果您想以明确所有权和生命周期的方式强烈地传递资源,那么智能指针是执行此操作的合适方法。标准库已经提供了它们,所以只需使用它们。

您已经发现这在很大程度上与自动存储持续时间的对象(您所说的“在堆栈上”)不兼容。您可以使用自定义的无操作删除器来解决它,但老实说为什么?你只是让生活对自己来说太复杂了。当您以这种方式创建对象时,将为您选择所有权和生命周期,并且很容易出错(悬空指针,伙计们!)。

只需让std::make_uniquestd::make_shared 为您动态分配Foos,并以通常的方式使用生成的智能指针。


但是,显然我们看不到您的设计,也不太了解它。

如果您只需将对象的引用传递给函数,函数会做一件事然后返回,然后就这样做!

void bar(const Foo& foo)
{
    // do stuff
}

int main()
{
   Foo foo;
   bar(foo);
}

【讨论】:

  • +1 是的,我可以直接通过它,但这会给所有函数添加很多参数,我宁愿以某种方式保存指针。在第一段中,您说我实际上应该动态分配所有内容。那么在 mcve 中,foo 将被动态分配,而ptr 将只指向资源?
  • @StackDanny 老实说,如果不知道您要做什么,我无法做出坚定的建议。 “但这会给所有函数添加很多参数”为什么?什么样的论据?引用如何比指针“更多参数”?再次,不清楚。
  • 同时将所有对象都放在堆栈上会限制它们的数量。我希望您不要期望在堆栈上存储 MB 的数据。
  • @LightnessRacesinOrbit 我有多个需要通过的对象。那是一个图形管道,在许多循环中有很多功能。在我的示例中,“应用程序”类控制程序的状态并与它们共享资源。所以每个循环调用 & 更新调用 & 绘图调用: 需要作为参数给出。感觉很丑。
  • 是的,抱歉,根本没听懂。您的描述中似乎缺少基本原理、限制和论点。也许这只是我。希望其他人可以更好地帮助您。
【解决方案2】:

但我不确定最佳策略是什么。指针?

可能。如果你需要指向一个对象,那么指针是一个不错的选择。

参考可能是另一个:

Foo foo{ 0 };
Foo& ref = foo;
ref = 2;

如果引用就足够了,那么它通常比指针更可取。

我在使用 std::shared_ptr / std::unique_ptr 时犯了错误吗?

是的。您获得了自动变量的所有权。这是不行的。

在第一个示例中,您制作了一个对象的副本,期望对副本的修改会影响原始对象。

【讨论】:

  • 谢谢,我知道我的智能指针有问题。是的,现在感觉正确的做法就是使用指针。
【解决方案3】:

std::reference_wrapper:

Foo foo{ 0 };
std::reference_wrapper<Foo> ref = foo;

ref.get().x = 2;

std::cout << "\nref.x : " << ref.get().x << '\n';
std::cout << "foo.x : " << foo.x << "\n\n";

输出 >

constructor

ref.x : 2
foo.x : 2

deconstructor

它的行为就像一个指针。好吧,看看它的实现它一个指针:

template<class _Ty>
    class reference_wrapper
        : public _Weak_types<_Ty>::type
    {
public:
    /*...*/
private:
    _Ty * _Ptr; //<---
    };

对于 pointer const 效果 (Foo* const),它可以声明为 const std::reference_wrapper&lt;Foo&gt; - 不允许对其指针地址进行任何更改。

Foo foo{ 0 };
const std::reference_wrapper<Foo> ref = foo;
Foo foo2{ 2 };
ref = foo2; //error

错误 C2678 二进制“=”:未找到采用“const std::reference_wrapper”类型的左操作数的运算符(或没有可接受的转换)

【讨论】:

    猜你喜欢
    • 2011-01-19
    • 1970-01-01
    • 1970-01-01
    • 2018-06-30
    • 1970-01-01
    • 2017-02-22
    • 1970-01-01
    • 2016-03-03
    • 1970-01-01
    相关资源
    最近更新 更多