매달린 const 참조를 해결하는 방법


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을 위해 또는 적어도 손실 된 데이터를 인식하기 위해) 이 데이터를 모델링하는 것이 좋습니다.
Scheff

C ++에서는 필요하지 않거나 사용하지 않는 것에 대한 비용을 지불하지 않습니다. 참조가 여전히 사용 중이거나 존재하는 동안 참조 된 개체의 수명이 끝나지 않도록주의하는 것은 프로그래머의 책임입니다. 원시 포인터에
대해서도 마찬가지입니다.

2
: 참조 멤버는 항상 실수 있습니다 herbsutter.com/2020/02/23/references-simply
맥심 Egorushkin

컴파일러가 경고하지는 않지만이 버그는 Valgrind 및에서 발견 할 수 -fsanitize=address있습니다. 나는 성능을 희생시키지 않고 그것을 피하는 모범 사례가 없다고 생각합니다.
ks1322

답변:


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. lvalue의 암시 적 바인딩을 허용하므로 기존의 유효한 호출을 중단하지 않습니다.
auto nums{someNums()};
Woop woop{nums}; // ok
woop.report();
  1. lvalue의 명시적인 바인딩을 허용하는데, 이는 호출자가 리턴 후 참조를 유지할 것임을 나타내는 좋은 방법입니다.
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되도록하려면 rvalue에서 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;
      |     ^~~~

추신 : 아마도 명시 적 생성자를 원할 것입니다. 예를 들어 명시 적 키워드의 의미는 무엇입니까?를 참조하십시오 . .


나는 당신의 대답을 도난당한 것 같습니다. 죄송합니다!
보석 테일러

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_ptr또는 std::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 correctness하고 compile time execution.

다음은 흥미로운 게시물입니다 : std :: 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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.