当试图指向自动对象时,应该使用什么策略

What strategy should be used when trying to point to automatic objects?

本文关键字:策略 什么 对象      更新时间:2023-10-16

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

这是我的简历:

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

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

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

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

输出>

constructor
foo.x : 0
destructor

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


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

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 << "nn";

输出>

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 << "nn";

输出>

constructor
ptr->x : 2
foo.x  : 2
destructor
destructor //crash

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

HEAP〔main.exe〕:为RtlValidateHeap(…)指定的地址无效


使用常量指针:

Foo foo{ 0 };
Foo* const ptr = &foo;
ptr->x = 2;
std::cout << "nptr->x : " << ptr->x << 'n';
std::cout << "foo.x  : " << foo.x << "nn";

输出>

constructor
ptr->x : 2
foo.x  : 2
deconstructor
  • 1个分配,1个解除分配
  • 禁止复制
  • 实际操作值

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


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

我自己提供了一个解决方案,那就是使用/*const*/ std::reference_wrapper<T>。(请随时纠正我的这个解决方案)

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

是否有更好、更直接的STL类

我在这一点上有点对冲,因为你的目标不明确。


如果您想以明确所有权和生存期的方式强力传递资源,那么智能指针是实现这一点的合适方法。标准库已经提供了它们,所以只需使用它们。

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

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


然而,显然我们看不到你的设计,也不太了解它。

如果您所需要做的只是将对对象的引用传递给函数,这将执行一件事然后返回,那么只需执行即可!

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

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

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

参考可能是另一个:

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

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

我使用std::shared_ptr/std::unique_ptr时出错了吗?

是。您获得了一个自动变量的所有权。这是不允许的。

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

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 << "nn";

输出>

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<Foo>,不允许更改其指针地址。

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

错误C2678二进制"=":找不到接受类型为"const std::reference_wrapper"的左侧操作数的运算符(或者没有可接受的转换)