如何解决悬空的const ref


18

以下简短程序

#include <vector>
#include <iostream>

std::vector<int> someNums()
{
    return {3, 5, 7, 11};
}

class Woop
{
public:
    Woop(const std::vector<int>& nums) : numbers(nums) {}
    void report()
    {
        for (int i : numbers)
            std::cout << i << ' ';
        std::cout << '\n';
    }
private:
    const std::vector<int>& numbers;
};

int main()
{
    Woop woop(someNums());
    woop.report();
}

有一个悬而未决的参考问题,似乎没有编译器警告。问题是临时对象可以绑定到const-refs,然后可以将其保留。问题是;有避免这种问题的方法吗?最好是不涉及牺牲const正确性或始终复制大对象的一种。


4
那很棘手。我可以向您保证,在进行成员变量const引用之前,请三思而后行。如有疑问,我将考虑以某种方式对数据进行建模,以便可以使用智能指针(std::unique_ptr对于专有所有权std::shared_ptr或共享所有权,或std::weak_ptr至少识别丢失的数据)。
谢夫

在C ++中,您无需为不需要/不需要的东西付费。程序员必须小心,当引用仍在使用/存在时,引用对象的生存期不会结束。原始指针也是一样,...有智能指针可以为您带来所需的功能:)
Fareanor


尽管编译器没有警告,但Valgrind和可以捕获此错误-fsanitize=address。我认为没有任何最佳实践可以在不牺牲性能的情况下避免它。
ks1322

Answers:


8

在某些方法返回后保留引用的情况下,最好使用std::reference_wrapper代替常规引用的方法:

#include <functional>

class Woop
{
public:
    using NumsRef = ::std::reference_wrapper<const std::vector<int>>;
    Woop(NumsRef nums) : numbers_ref{nums} {}
    void report()
    {
        for (int i : numbers_ref.get())
            std::cout << i << ' ';
        std::cout << '\n';
    }
private:
    NumsRef numbers_ref;
};
  1. 它已经带有一组重载,可以防止rvalue绑定和意外的临时传递,因此不需要Woop (std::vector<int> const &&) = delete;为方法使用rvalue的额外禁止的重载打扰:
Woop woop{someNums()}; // error
woop.report();
  1. 它允许左值的隐式绑定,因此不会破坏现有的有效调用:
auto nums{someNums()};
Woop woop{nums}; // ok
woop.report();
  1. 它允许显式绑定左值,这是一个好习惯,它表明调用者在返回后将保留引用:
auto nums{someNums()};
Woop woop{::std::ref(nums)}; // even better because explicit
woop.report();

10

使您的类更不容易受到攻击的一种方法是添加一个删除的构造函数,该构造函数需要一个右引用。这将阻止您的类实例绑定到临时对象。

Woop(std::vector<int>&& nums)  =delete;

这个删除的构造函数实际上会使O / P代码无法编译,这可能是您正在寻找的行为吗?


3

我同意其他答案和评论,如果您确实需要在类中存储引用,则应仔细考虑。而且,如果这样做,您可能希望改为使用const向量的非const指针(即std::vector<int> const * numbers_)。

但是,如果是这样,我发现其他当前发布的答案不在重点之列。他们都在向您展示如何实现Woop这些价值观。

如果可以确保传入的向量将超过Woop实例的寿命,则可以显式禁用Woop从右值构造a 。使用以下C ++ 11语法是可能的:

Woop (std::vector<int> const &&) = delete;

现在,您的示例代码将不再编译。编译器给出的错误类似于:

prog.cc: In function 'int main()':
prog.cc:29:25: error: use of deleted function 'Woop::Woop(const std::vector<int>&&)'
   29 |     Woop woop(someNums());
      |                         ^
prog.cc:15:5: note: declared here
   15 |     Woop(std::vector<int> const &&) = delete;
      |     ^~~~

PS:您可能需要一个显式构造函数,请参见例如,explicit关键字是什么意思?


我似乎在那儿偷了你的答案。抱歉!
宝石泰勒

1

为了防止发生这种情况,您可以选择采用一个指针(因为Weep(&std::vector<int>{1,2,3})不允许这样做),也可以采用非常量引用,这也会在临时情况下出错。

Woop(const std::vector<int> *nums);
Woop(std::vector<int> *nums);
Woop(std::vector<int>& nums);

这些仍然不能保证该值仍然有效,但是至少可以停止最简单的错误,不创建副本,也不需要nums以特殊方式创建(例如,std::shared_ptrstd::weak_ptr确实)。

std::scoped_lock引用互斥量将是一个示例,而实际上并不需要唯一/共享/弱ptr。通常,它们std::mutex只会是基本成员或局部变量。您仍然必须非常小心,但是在这些情况下,通常很容易确定使用寿命。

std::weak_ptr是非拥有的另一种选择,但是您随后迫使调用者使用shared_ptr(因此也分配了堆分配),有时这是不需要的。

如果可以,则可以避免该问题。

如果Woop应该拥有所有权,则要么将其作为r值传递并移动(并完全避免指针/引用问题),要么unique_ptr如果不能移动值本身或希望指针保持有效,请使用。

// the caller can't continue to use nums, they could however get `numbers` from Woop or such like
// or just let Woop only manipulate numbers directly.
Woop(std::vector<int> &&nums) 
   : numbers(std::move(nums)) {}
std::vector<int> numbers;

// while the caller looses the unique_ptr, they might still use a raw pointer, but be careful.
// Or again access numbers only via Woop as with the move construct above.
Woop(std::unique_ptr<std::vector<int>> &&nums) 
    : numbers(std::move(nums)) {}
std::unique_ptr<std::vector<int>> numbers;

或者,如果所有权是共享的,则可以使用shared_ptr所有内容,并且它将与最终引用一起删除,但是如果过度使用,这会使跟踪对象生命周期变得非常混乱。


1

您可以使用template programming并且,arrays如果您想拥有一个const容纳容器的对象。由于constexpr构造函数,constexpr arrays您可以实现const correctnesscompile time execution

这是一篇可能有趣的文章:std :: move const vector

#include <array>
#include <iostream>
#include <vector>


std::array<int,4>  someNums()
{
    return {3, 5, 7, 11};
}


template<typename U, std::size_t size>
class Woop
{
public:

template<typename ...T>
    constexpr Woop(T&&... nums) : numbers{nums...} {};

    template<typename T, std::size_t arr_size>
    constexpr Woop(std::array<T, arr_size>&& arr_nums) : numbers(arr_nums) {};

    void report()
    const {
        for (auto&& i : numbers)
            std::cout << i << ' ';
         std::cout << '\n';
    }



private: 
    const std::array<U, size> numbers;
    //constexpr vector with C++20
};

int main()
{
    Woop<int, 4> wooping1(someNums());
    Woop<int, 7> wooping2{1, 2, 3, 5, 12 ,3 ,51};

    wooping1.report();
    wooping2.report();
    return 0;
}

运行代码

输出:

3 5 7 11                                                                                                                        
1 2 3 5 12 3 51

1
使用数字作为std::array保证可以复制,即使可以采取其他行动也是如此。最重要的是wooping1wooping2它们不是同一类型,这不理想。
sp2danny

@ sp2danny感谢您的反馈,我必须在两点上都同意您的观点。user7860670提供了更好的解决方案:)
M.Mac,
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.