c++ 使用shared_ptr销毁后shared_ptr引用的对象中的std::vector

raogr8fs  于 2023-02-10  发布在  其他
关注(0)|答案(3)|浏览(258)

我很抱歉,如果标题是不同的,我将描述,我不太知道如何描述它除了使用例子。
假设我有一个对象的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适合使用吗?

jbose2ul

jbose2ul1#

使用这个变量安全吗?
是的,在下面的语句中,您使用std::vector::operator=重载进行复制赋值来 * 复制 * 整个vector。这两个vector不共享任何东西,它们各自独立,可以彼此独立使用。

input = hehe->vektor;
yvfmudvl

yvfmudvl2#

在这种情况下是安全的,因为在这一行中得到了向量的副本:

input = hehe->vektor;

一个很大的区别是,在我的示例中,我通过函数之间的引用传递了std::vector,而不是全局变量,但我希望这不会改变行为。
任何引用都只能绑定一次,并且在您的场景中,input引用已经绑定到传递的参数(确切地说,inner函数的std::vector<int>& input绑定到outer函数的std::vector<int>& input,而outer函数本身绑定到std::vector<int> vector_to_populate)绑定引用后,作为对象本身。所以在赋值语句中你实际上调用了这样的语句:

input.operator=(hehe->vektor);

其中operator=指的是std::vector<T>::operator=(const std::vector<T> rhs)函数。

mqkwyuun

mqkwyuun3#

    • 如果您熟悉RAII概念和C++参考资料,则几乎可以跳过以下说明**

在C++中,向量,事实上,几乎所有的结构体,在std中定义的工作方式与你在其他高级语言中使用的工作方式非常不同,比如Java或C#。(资源获取即初始化)技术,我强烈建议你实际阅读这篇文章,但总之,这意味着一个对象将定义一个构造函数和一个析构函数,它们分配和释放该对象使用的所有内存,在这里是一个std::vector,当对象超出作用域时,语言将调用析构函数,这确保了没有内存泄漏。
然而,我们如何将一个std::vector传递给一个函数呢?如果我们直接复制对象,一个字节接一个字节,就像我们在C中做的那样,函数会运行良好,直到我们到达函数的末尾,然后调用向量的析构函数。在这种情况下,在函数执行之后,当我们返回调用者时,向量不再有效,因为它的数据被被调用者释放。

void callee(std::vector<int> vec) { }
void caller() {
    std::vector<int> vec;
    vec.push_back(10);
    callee(vec);
    vec.push_back(10); // This will break, with our current logic
}

这里的关键字是"copy"。我们复制了这个向量,这样我们就可以在callee函数中传递它。我们可以通过创建自定义复制行为来解决这个问题,用C的术语来说就是一个复制构造函数。简单地说,复制构造函数将类型本身的一个示例作为参数,并将其复制到当前示例中。这允许我们现在执行上面的代码而没有任何问题。
它比我在这里写的要复杂得多,但其他人说得比我好。简而言之,无论何时,当你试图进行赋值,或传递参数时,你都要利用复制构造函数(有一些例外)。在你的例子中,你给一个向量变量赋一个向量值。这个向量值被复制,所以在函数超出作用域之前都是有效的。这里有一个陷阱:如果你试图修改向量,你将只修改拷贝,而不是原始的。如果你想这样做,你需要使用references
在C
中,我们有引用的概念。你可以把引用看作指针,但要注意一些问题。首先,与指针不同的是,你不能有对引用的引用。引用告诉C++你不使用对象本身,而是对象的"别名"。对象将存在于内存中的一个地方。但您可以在两个位置访问它:

int a = 10;
int &ref_a = a;

std::cout << ref_a << ", " << a << "\n"; // 10, 10
ref_a = 5;
std::cout << ref_a << ", " << a << "\n"; // 5, 5
a = 8;
std::cout << ref_a << ", " << a << "\n"; // 8, 8

在行int &ref_a = a中,我们实际上并没有复制a,而是告诉ref_a它是a的一个引用。(包括赋值)将应用于a,而不是ref_a。我们可以使用变量,字段,返回值和参数。只要引用的值有效,引用就有效,因此一旦值超出范围,引用就不再有效。
为了避免复制值,可以在参数中使用引用。这可以提供很多性能优势,因为我们不需要复制值,而只需要传递一个指向它的"指针"。当然,这意味着如果函数修改了参数,这将反映在调用者中:

void func(int &ref) {
   ref = 5;
}

void func2() {
    int a = 10;
    func(a);
    std::cout << a; // 5
}

靶区; DR

在本例中,您通过out参数返回向量(引用参数)。尽管如此,即使你使用的是引用,设置引用实际上会在引用后面设置对象,并且会使用对象的复制构造函数。你可以通过将其设置为指向向量的指针的引用来避免这种情况,但是强烈建议不要在C++中使用指针。无论如何,你的问题的答案是这段代码是完全安全的,但是,如果你试图在outer中修改input,你不会修改hehe中的向量,而是修改inener创建的副本。

相关问题