【问题标题】:How to resolve dangling const ref如何解决悬空的 const ref
【发布时间】:2020-03-10 11:23:32
【问题描述】:

下面的小程序

#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 正确性,或总是使 大对象的副本。

【问题讨论】:

  • 这很棘手。我可以向您保证,我在创建成员变量 const 引用之前会三思而后行。如果有疑问,我会考虑以某种方式对这些数据进行建模,以便涉及智能指针(std::unique_ptr 用于独占所有权,std::shared_ptr 或共享所有权,或std::weak_ptr 至少可以识别丢失的数据)。
  • 在 C++ 中,您无需为不需要/使用的东西付费。程序员应注意当引用仍在使用/存在时,引用对象的生命周期不会结束。原始指针也是如此,......有智能指针可以为您带来您要求的功能:)
  • 引用成员总是一个错误:herbsutter.com/2020/02/23/references-simply
  • 虽然编译器没有发出警告,但这个错误可以被 Valgrind 和-fsanitize=address 捕获。我认为没有任何最佳做法可以在不牺牲性能的情况下避免它。

标签: c++


【解决方案1】:

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

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

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

【讨论】:

    【解决方案2】:

    在某些方法返回后保留引用的情况下,最好使用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. 它已经带有一组重载,可防止右值绑定和临时变量的意外传递,因此无需为您的方法采用右值 Woop (std::vector&lt;int&gt; const &amp;&amp;) = delete; 的额外禁止重载而烦恼:
    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();
    

    【讨论】:

      【解决方案3】:

      如果您确实需要在类中存储引用,我同意您应该仔细考虑的其他答案和 cmets。如果这样做,您可能需要一个指向 const 向量的非 const 指针(即std::vector&lt;int&gt; const * numbers_)。

      但是,如果是这样的话,我发现其他当前发布的答案是不重要的。他们都在向你展示如何让 Woop 拥有这些价值观。

      如果您可以确保传入的向量比您的 Woop 实例寿命更长,那么您可以明确禁止从右值构造 Woop。使用这种 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;
            |     ^~~~
      

      P.S.:您可能需要一个显式构造函数,请参见例如What does the explicit keyword mean?.

      【讨论】:

      • 我似乎在那里偷了你的答案。对不起!
      【解决方案4】:

      为防止出现这种特殊情况,您可以选择采用指针(因为不允许使用 Weep(&amp;std::vector&lt;int&gt;{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,它将与最终引用一起被删除,但是如果过度使用,这会使跟踪对象生命周期变得非常混乱。

      【讨论】:

        【解决方案5】:

        如果您想拥有一个包含const 容器的对象,您可以使用template programmingarrays。由于constexpr 构造函数和constexpr arrays 你实现了const correctnesscompile time execution

        这是一个可能很有趣的帖子:std::move a 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;
        }
        

        run code

        输出:

        3 5 7 11                                                                                                                        
        1 2 3 5 12 3 51
        

        【讨论】:

        • 如果数字为std::array,则保证可以复制,即使可以进行移动。此外,wooping1wooping2 不是同一类型,这不太理想。
        • @sp2danny 感谢您的反馈,我在这两点上都同意您的看法。 user7860670 提供了更好的解决方案:)
        猜你喜欢
        • 2011-10-05
        • 1970-01-01
        • 1970-01-01
        • 2018-10-13
        • 2021-12-13
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-10-02
        相关资源
        最近更新 更多