我正在开发一个相对较大的库(~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_ptr
或std::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 << "\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
得到正常的解构,而std::shared_ptr
也想要解构其已解构的解构值。
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
到目前为止,指针感觉是最好的选择。
我只是想找到一种指向自动分配对象的方法。到目前为止,如果不回到简单的指针,它似乎非常困难。
我自己提供了一个解决方案 - 使用/*const*/ std::reference_wrapper<T>
。 (随意纠正我这个解决方案)
但我不确定什么是最好的策略。指针?也许std::reference_wrapper
?我使用std::shared_ptr
/ std::unique_ptr
犯了错误吗?
是否有更好,更直接的STL课程?
但我不确定什么是最好的策略。指针?
有可能。如果你需要指向一个对象,那么指针可能是一个不错的选择。
参考可能是另一个:
Foo foo{ 0 };
Foo& ref = foo;
ref = 2;
如果引用足够,那么它通常比指针更好。
我使用
std::shared_ptr
/std::unique_ptr
犯了错误吗?
是。您获得了自动变量的所有权。这是禁忌。
在第一个示例中,您制作了一个对象的副本,期望修改副本以影响原始对象。
因为你的目标尚不明确,所以我会对这一点进行一些抑制。
如果您希望以一种使所有权和生命周期清晰的方式强有力地传递资源,那么智能指针就是这样做的合适方式。标准库已经提供了它们,所以只需使用它们即可。
您已经发现这与自动存储持续时间(您在堆栈中称为“”)的对象很大程度上不兼容。您可以使用自定义的无操作删除器解决它,但老实说为什么?你只是让自己的生活太复杂了。当您以这种方式创建对象时,将为您选择所有权和生命周期,并且容易出错(悬空指针,伙计!)。
只需让std::make_unique
或std::make_shared
为你动态分配你的Foo
s,并以通常的方式使用生成的智能指针。
然而,显然我们看不到你的设计,也不了解它。
如果你需要做的就是将对象的引用传递给函数,然后返回,然后简单地执行!
void bar(const Foo& foo)
{
// do stuff
}
int main()
{
Foo foo;
bar(foo);
}
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<Foo>
- 禁止其指针地址的任何更改。
Foo foo{ 0 };
const std::reference_wrapper<Foo> ref = foo;
Foo foo2{ 2 };
ref = foo2; //error
错误C2678二进制'=':找不到哪个运算符带有'const std :: reference_wrapper'类型的左操作数(或者没有可接受的转换)