IT Share you

Herb Sutter의 CppCon 2014 talk (Back to Basics : Modern C ++ Style)에서 값을 취하는 setter 멤버 함수가 권장되지 않는 이유는 무엇입니까?

shareyou 2020. 11. 28. 13:16
반응형

Herb Sutter의 CppCon 2014 talk (Back to Basics : Modern C ++ Style)에서 값을 취하는 setter 멤버 함수가 권장되지 않는 이유는 무엇입니까?


Herb Sutter의 CppCon 2014에서 Back to Basics : Modern C ++ Style에 대해 그는 슬라이드 28 (슬라이드 의 웹 사본이 여기에 있음 )에서이 패턴을 언급했습니다.

class employee {
  std::string name_;
public:
  void set_name(std::string name) noexcept { name_ = std::move(name); }
};

그는 임시로 set_name ()을 호출 할 때 noexcept-ness가 강하지 않기 때문에 이것이 문제라고 말합니다 (그는 "noexcept-ish"라는 문구를 사용합니다).

이제는 매번 set_name () 복사본을 두 개 입력 할 필요가 없기 때문에 최근 C ++ 코드에서 위의 패턴을 꽤 많이 사용하고 있습니다. 매번 복사본 생성을 강제하면 약간 비효율적 일 수 있습니다. 하지만 나는 게으른 타이 퍼입니다. 그러나 허브의 문구 "이 noexcept이 문제는 표준 : : 문자열의 이동 할당 연산자가 noexcept 바와 같이, 소멸자이다 SET_NAME 있도록 () 위의 보장 noexcept로 나에게 보인다 : 여기 문제를하지 않는 한"나를 걱정. 매개 변수를 준비 할 때 set_name () 전에 컴파일러에서 잠재적 인 예외가 발생 하는 것을 볼 수 있지만 문제가있는 것으로 확인하기 위해 고군분투하고 있습니다.

나중에 슬라이드 32에서 Herb는 위의 내용이 반 패턴임을 분명히 밝힙니다. 누군가 내가 게으름으로써 나쁜 코드를 작성하지 않도록 왜 나에게 설명 할 수 있습니까?


다른 사람들은 noexcept이유를 다루었습니다 .

Herb는 효율성 측면에 대한 이야기에서 훨씬 더 많은 시간을 보냈습니다. 문제는 할당이 아니라 불필요한 할당 해제입니다. 복사 할 때std::string복사 루틴은 복사중인 데이터를 저장할 충분한 공간이있는 경우 대상 문자열의 할당 된 저장소를 재사용합니다. 이동 할당을 수행 할 때 대상 문자열의 기존 저장소는 소스 문자열에서 저장소를 인수하므로 할당을 취소해야합니다. "복사 및 이동"관용구는 임시를 전달하지 않더라도 할당 해제가 항상 발생하도록합니다. 이것이 나중에 강연에서 시연되는 끔찍한 공연의 원천입니다. 그의 조언은 대신 const ref를 사용하고 필요한 경우 r 값 참조에 대한 과부하가 필요하다는 것이 었습니다. 이렇게하면 두 가지 장점을 모두 얻을 수 있습니다. 임시가 아닌 사용자를 위해 기존 스토리지에 복사하여 할당 해제를 피하고 임시로 이동합니다.

할당을 해제 할 멤버 변수에 저장소가 없기 때문에 위의 내용은 생성자에 적용되지 않습니다. 생성자는 종종 하나 이상의 인수를 취하고 각 인수에 대해 const ref / r-value ref 오버로드를 수행해야하는 경우 생성자 오버로드의 조합 폭발이 발생하기 때문에 이것은 좋습니다.

이제 질문은 다음과 같습니다. 복사 할 때 std :: string과 같은 저장소를 재사용하는 클래스가 몇 개입니까? 나는 std :: vector가 그렇다고 생각하지만 그 외에는 확실하지 않습니다. 이런 저장소를 재사용하는 클래스를 작성한 적이 없다는 것을 알고 있지만 문자열과 벡터를 포함하는 많은 클래스를 작성했습니다. Herb의 조언을 따르면 스토리지를 재사용하지 않는 클래스에 대해 해를 끼치 지 않을 것입니다. 처음에는 싱크 함수의 복사 버전으로 복사하고 복사가 성능에 너무 많은 영향을 미친다고 판단하면 그런 다음 복사를 피하기 위해 r 값 참조 오버로드를 만드십시오 (std :: string에서와 마찬가지로). 반면에 "복사 및 이동"을 사용하면 std :: string 및 스토리지를 재사용하는 기타 유형에 대해 성능 저하가 입증되었습니다. 이러한 유형은 대부분의 사람들 코드에서 많이 사용됩니다. 나는 지금 Herb의 조언을 따르고 있지만, 문제가 완전히 해결되었다고 생각하기 전에 이것의 일부를 좀 더 생각해볼 필요가 있습니다. (이 모든 것에 숨어있는 글을 쓸 시간이없는 블로그 게시물이있을 것입니다).


값으로 전달하는 것이 const 참조로 전달하는 것보다 더 나은 이유에는 두 가지 이유가 있습니다.

  1. 더 효율적
  2. 아니요

유형 멤버에 대한 setter의 경우 std::string, 그는 const 참조를 통한 전달이 일반적으로 더 적은 할당을 생성한다는 것을 보여줌으로써 값으로 전달하는 것이 더 효율적이라는 주장을 반박했습니다 std::string.

그는 또한 noexcept매개 변수를 복사하는 과정에서 예외가 여전히 발생할 수 있기 때문에 noexcept 선언이 오해의 소지가 있음 을 보여줌으로써 setter가 허용된다는 주장을 반박했습니다.

따라서 그는 적어도이 경우에는 const 참조로 전달하는 것이 값으로 전달하는 것보다 선호한다고 결론지었습니다. 그러나 그는 값으로 전달하는 것이 생성자에게 잠재적으로 좋은 접근 방식이라고 언급했습니다.

나는 예제 std::string만으로는 모든 유형을 일반화하기에 충분 하지 않다고 생각 하지만, 적어도 효율성과 예외적 인 이유로 인해 복사 비용이 많이 들지만 이동 비용이 저렴한 매개 변수를 값별로 전달하는 관행에 의문을 제기합니다.


Herb에는 이미 스토리지가 할당되어있을 때 가치를 고려하는 것이 비효율적이며 불필요한 할당이 발생할 수 있다는 점에서 요점이 있습니다. 그러나 받아들이는 const&것은 마치 원시 C 문자열을 가져 와서 함수에 전달하면 불필요한 할당이 발생하는 것처럼 거의 나쁩니다.

당신이 취해야 할 것은 문자열 자체가 아니라 문자열에서 읽는 추상화입니다.

이제 다음과 같이 할 수 있습니다 template.

class employee {
  std::string name_;
public:
  template<class T>
  void set_name(T&& name) noexcept { name_ = std::forward<T>(name); }
};

합리적으로 효율적입니다. 그런 다음 SFINAE를 추가하십시오.

class employee {
  std::string name_;
public:
  template<class T>
  std::enable_if_t<std::is_convertible<T,std::string>::value>
  set_name(T&& name) noexcept { name_ = std::forward<T>(name); }
};

그래서 우리는 구현이 아닌 인터페이스에서 오류를 얻습니다.

구현을 공개적으로 노출해야하므로 항상 실용적인 것은 아닙니다.

string_view유형 클래스가 들어올 수 있는 곳 은 다음과 같습니다.

template<class C>
struct string_view {
  // could be private:
  C const* b=nullptr;
  C const* e=nullptr;

  // key component:
  C const* begin() const { return b; }
  C const* end() const { return e; }

  // extra bonus utility:
  C const& front() const { return *b; }
  C const& back() const { return *std::prev(e); }

  std::size_t size() const { return e-b; }
  bool empty() const { return b==e; }

  C const& operator[](std::size_t i){return b[i];}

  // these just work:
  string_view() = default;
  string_view(string_view const&)=default;
  string_view&operator=(string_view const&)=default;

  // myriad of constructors:
  string_view(C const* s, C const* f):b(s),e(f) {}

  // known continuous memory containers:
  template<std::size_t N>
  string_view(const C(&arr)[N]):string_view(arr, arr+N){}
  template<std::size_t N>
  string_view(std::array<C, N> const& arr):string_view(arr.data(), arr.data()+N){}
  template<std::size_t N>
  string_view(std::array<C const, N> const& arr):string_view(arr.data(), arr.data()+N){}
  template<class... Ts>
  string_view(std::basic_string<C, Ts...> const& str):string_view(str.data(), str.data()+str.size()){}
  template<class... Ts>
  string_view(std::vector<C, Ts...> const& vec):string_view(vec.data(), vec.data()+vec.size()){}
  string_view(C const* str):string_view(str, str+len(str)) {}
private:
  // helper method:
  static std::size_t len(C const* str) {
    std::size_t r = 0;
    if (!str) return r;
    while (*str++) {
      ++r;
    }
    return r;
  }
};

이러한 객체는 std::string또는 a 에서 직접 구성 할 수 있으며 새 객체 "raw C string"를 생성하기 위해 알아야하는 것을 거의 비용없이 저장할 수 있습니다 std::string.

class employee {
  std::string name_;
public:
  void set_name(string_view<char> name) noexcept { name_.assign(name.begin(),name.end()); }
};

이제 우리 set_name는 고정 된 인터페이스 (완벽한 포워드 인터페이스가 아님)를 가지므로 구현이 보이지 않을 수 있습니다.

유일한 비 효율성은 C 스타일 문자열 포인터를 전달하면 다소 불필요하게 크기를 두 번 초과한다는 것입니다 (처음에는을 찾고 '\0'두 번째로 복사). 반면에 이것은 얼마나 큰지에 대한 타겟 정보를 제공하므로 재 할당하는 대신 사전 할당 할 수 있습니다.


이 메서드를 호출하는 방법에는 두 가지가 있습니다.

  • With rvalue parameter, as long as the move constructor of the parameter type is noexcept there is no problem (in case of std::string most probably is noexcept), in any case would be better use a conditional noexcept (to be sure the parameters is noexcept)
  • With lvalue parameter, in this case the copy constructor of the parameter type would be called and almost sure it will need some allocation (that could throw).

In cases like this that the use could be miss used, it's better to avoid. The client of the class assume that no exception is throw as specified, but in valid, compilable, not suspicious C++11 could throw.

참고URL : https://stackoverflow.com/questions/26261007/why-is-value-taking-setter-member-functions-not-recommended-in-herb-sutters-cpp

반응형