我很抱歉,如果标题是不同的,我将描述,我不太知道如何描述它除了使用例子。
假设我有一个对象的shared_ptr
,在这个对象中有一个向量,我把这个向量赋给一个变量,这样我就可以在以后访问它,shared_ptr
在超出作用域时被销毁了,问题是,我保存的向量对于访问来说是“安全的”吗?
在下面的示例中,从main()
调用outer()
,在outer()
中调用inner()
。inner()
为包含std::vector
的对象创建shared_ptr
,并将其赋给通过引用传递的变量。outer()
的作用是创建某种形式的分隔,这样我们就知道shared_ptr
被破坏了,在main()
中,这个被引用的变量是被访问的,但是使用这个变量安全吗?
#include <iostream>
#include <vector>
#include <memory>
struct sample_compound_obj {
std::vector<int> vektor;
sample_compound_obj(){std::cout << "I'm alive!" << std::endl;};
~sample_compound_obj(){std::cout << "Goodbye, thank you forever!" << std::endl;};
};
bool inner(std::vector<int>& input) {
std::cout << "About to create sample_compound_obj..." << std::endl;
std::shared_ptr<sample_compound_obj> hehe(new sample_compound_obj);
hehe->vektor.push_back(1);
hehe->vektor.push_back(2);
hehe->vektor.push_back(3);
input = hehe->vektor;
std::cout << "About to return from inner()..." << std::endl;
return true;
}
bool outer(std::vector<int>& input) {
std::cout << "About to enter inner()..." << std::endl;
inner(input);
std::cout << "About to return from outer()..." << std::endl;
return true;
}
int main() {
std::cout << "About to enter outer()..." << std::endl;
std::vector<int> vector_to_populate;
outer(vector_to_populate);
for (std::vector<int>::iterator it = vector_to_populate.begin(); it != vector_to_populate.end(); it++) {
std::cout << *it <<std::endl; // <-- is it even "safe" to access this vector
}
}
https://godbolt.org/z/47EWfPGK3
为了避免XY问题,我在编写ROS代码时第一次想到了这个问题,其中订阅者回调函数引用传入的消息为const shared_ptr&
,消息中包含std::vector
,在这个回调函数中,std::vector
被赋值(通过=
)到一个全局/成员变量,在回调结束后的一段时间后使用,所以大概原始的shared_ptr
被破坏了。一个很大的区别是,在我的例子中,我在函数之间通过引用传递了std::vector
,而不是全局变量,但我希望它不会改变行为。问题是,我“保存”的std::vector
适合使用吗?
3条答案
按热度按时间jbose2ul1#
使用这个变量安全吗?
是的,在下面的语句中,您使用
std::vector::operator=
重载进行复制赋值来 * 复制 * 整个vector
。这两个vector
不共享任何东西,它们各自独立,可以彼此独立使用。yvfmudvl2#
在这种情况下是安全的,因为在这一行中得到了向量的副本:
一个很大的区别是,在我的示例中,我通过函数之间的引用传递了std::vector,而不是全局变量,但我希望这不会改变行为。
任何引用都只能绑定一次,并且在您的场景中,
input
引用已经绑定到传递的参数(确切地说,inner
函数的std::vector<int>& input
绑定到outer
函数的std::vector<int>& input
,而outer
函数本身绑定到std::vector<int> vector_to_populate
)绑定引用后,作为对象本身。所以在赋值语句中你实际上调用了这样的语句:其中
operator=
指的是std::vector<T>::operator=(const std::vector<T> rhs)
函数。mqkwyuun3#
在C++中,向量,事实上,几乎所有的结构体,在
std
中定义的工作方式与你在其他高级语言中使用的工作方式非常不同,比如Java或C#。(资源获取即初始化)技术,我强烈建议你实际阅读这篇文章,但总之,这意味着一个对象将定义一个构造函数和一个析构函数,它们分配和释放该对象使用的所有内存,在这里是一个std::vector
,当对象超出作用域时,语言将调用析构函数,这确保了没有内存泄漏。然而,我们如何将一个
std::vector
传递给一个函数呢?如果我们直接复制对象,一个字节接一个字节,就像我们在C中做的那样,函数会运行良好,直到我们到达函数的末尾,然后调用向量的析构函数。在这种情况下,在函数执行之后,当我们返回调用者时,向量不再有效,因为它的数据被被调用者释放。这里的关键字是"copy"。我们复制了这个向量,这样我们就可以在
callee
函数中传递它。我们可以通过创建自定义复制行为来解决这个问题,用C的术语来说就是一个复制构造函数。简单地说,复制构造函数将类型本身的一个示例作为参数,并将其复制到当前示例中。这允许我们现在执行上面的代码而没有任何问题。它比我在这里写的要复杂得多,但其他人说得比我好。简而言之,无论何时,当你试图进行赋值,或传递参数时,你都要利用复制构造函数(有一些例外)。在你的例子中,你给一个向量变量赋一个向量值。这个向量值被复制,所以在函数超出作用域之前都是有效的。这里有一个陷阱:如果你试图修改向量,你将只修改拷贝,而不是原始的。如果你想这样做,你需要使用references。
在C中,我们有引用的概念。你可以把引用看作指针,但要注意一些问题。首先,与指针不同的是,你不能有对引用的引用。引用告诉C++你不使用对象本身,而是对象的"别名"。对象将存在于内存中的一个地方。但您可以在两个位置访问它:
在行
int &ref_a = a
中,我们实际上并没有复制a
,而是告诉ref_a
它是a
的一个引用。(包括赋值)将应用于a
,而不是ref_a
。我们可以使用变量,字段,返回值和参数。只要引用的值有效,引用就有效,因此一旦值超出范围,引用就不再有效。为了避免复制值,可以在参数中使用引用。这可以提供很多性能优势,因为我们不需要复制值,而只需要传递一个指向它的"指针"。当然,这意味着如果函数修改了参数,这将反映在调用者中:
靶区; DR
在本例中,您通过out参数返回向量(引用参数)。尽管如此,即使你使用的是引用,设置引用实际上会在引用后面设置对象,并且会使用对象的复制构造函数。你可以通过将其设置为指向向量的指针的引用来避免这种情况,但是强烈建议不要在C++中使用指针。无论如何,你的问题的答案是这段代码是完全安全的,但是,如果你试图在
outer
中修改input
,你不会修改hehe
中的向量,而是修改inener
创建的副本。