멤버 변수를 클래스 멤버로 참조


85

제 직장에서는이 스타일이 광범위하게 사용되는 것을 봅니다.

#include <iostream>

using namespace std;

class A
{
public:
   A(int& thing) : m_thing(thing) {}
   void printit() { cout << m_thing << endl; }

protected:
   const int& m_thing; //usually would be more complex object
};


int main(int argc, char* argv[])
{
   int myint = 5;
   A myA(myint);
   myA.printit();
   return 0;
}

이 관용구를 설명하는 이름이 있습니까? 나는 그것이 크고 복잡한 객체를 복사하는 데 따르는 큰 오버 헤드를 방지하는 것이라고 가정하고 있습니다.

이것은 일반적으로 좋은 습관입니까? 이 접근 방식에 함정이 있습니까?


4
한 가지 가능한 함정은 멤버 변수에서 참조하는 객체가 다른 곳에서 파괴되고 클래스를 통해 액세스하려고하는 경우입니다
mathematician1975

답변:


116

이 관용구를 설명하는 이름이 있습니까?

UML에서는 집계라고합니다. 멤버 개체가 참조 클래스에 의해 소유 되지 않는다는 점에서 구성과 다릅니다 . C ++에서는 참조 또는 포인터를 통해 두 가지 방법으로 집계를 구현할 수 있습니다.

나는 그것이 크고 복잡한 객체를 복사하는 데 따르는 큰 오버 헤드를 방지하는 것이라고 가정하고 있습니다.

아니요, 이걸 사용하는 것은 정말 나쁜 이유입니다. 집계의 주된 이유는 포함 된 개체가 포함 된 개체의 소유가 아니므로 해당 수명이 바인딩되지 않기 때문입니다. 특히 참조 된 개체 수명은 참조하는 개체 수명보다 길어야합니다. 훨씬 더 일찍 만들어 졌을 수 있으며 컨테이너의 수명이 끝난 후에도 살 수 있습니다. 그 외에도 참조 된 객체의 상태는 클래스에 의해 제어되지 않지만 외부 적으로 변경 될 수 있습니다. 참조가 아닌 const경우 클래스는 외부에있는 객체의 상태를 변경할 수 있습니다.

이것은 일반적으로 좋은 습관입니까? 이 접근 방식에 함정이 있습니까?

디자인 도구입니다. 어떤 경우에는 좋은 생각이 될 것이고 어떤 경우에는 그렇지 않을 것입니다. 가장 일반적인 함정은 참조를 보유한 개체의 수명이 참조 된 개체의 수명을 초과해서는 안된다는 것입니다. 주변 개체가 참조 된 개체가 소멸 된 참조를 사용하는 경우 정의되지 않은 동작이 발생합니다. 일반적으로 집계보다 컴포지션을 선호하는 것이 좋지만 필요한 경우 다른 도구만큼 좋은 도구입니다.


7
"아니요, 이걸 사용하는 것은 정말 나쁜 이유입니다." 이 점에 대해 자세히 설명해 주시겠습니까? 그것을 달성하기 위해 대신 무엇을 사용할 수 있습니까?
coincoin

@coincoin : 정확히 무엇을 달성하기 위해?
데이비드 로드리게스는 - dribeas

3
to prevent the possibly large overhead of copying a big complex object?
coincoin

3
@underscore_d 귀하의 답변에 감사드립니다. 그런 다음 둘 중 하나를 사용할 수 없으면 어떻게됩니까? 다른 클래스 내에서 동일한 객체를 공유하고 싶다고 상상해보십시오. 이 멤버 객체를 값으로 전달하면 각 클래스에 대한 객체의 복사본이 생깁니다. 따라서 해결책은 스마트 포인터 또는 참조를 사용하여 복사를 방지하는 것입니다. 아니 ?
coincoin

2
@ plats1 내가 방금 쓴 것입니다. 내 요점은 스마트 포인터 또는 참조를 사용할 수 있다는 것입니다.
coincoin

37

생성자 주입을 통한 종속성 주입 이라고합니다 . 클래스 A는 종속성을 생성자에 대한 인수로 가져오고 종속 클래스에 대한 참조를 전용 변수로 저장합니다.

wikipedia에 흥미로운 소개가 있습니다.

const 정확성을 위해 다음과 같이 작성합니다.

using T = int;

class A
{
public:
  A(const T &thing) : m_thing(thing) {}
  // ...

private:
   const T &m_thing;
};

그러나이 클래스의 문제점은 임시 객체에 대한 참조를 허용한다는 것입니다.

T t;
A a1{t};    // this is ok, but...

A a2{T()};  // ... this is BAD.

추가하는 것이 더 좋습니다 (적어도 C ++ 11 필요).

class A
{
public:
  A(const T &thing) : m_thing(thing) {}
  A(const T &&) = delete;  // prevents rvalue binding
  // ...

private:
  const T &m_thing;
};

어쨌든 생성자를 변경하면 :

class A
{
public:
  A(const T *thing) : m_thing(*thing) { assert(thing); }
  // ...

private:
   const T &m_thing;
};

임시에 대한 포인터가 없다는 것이 거의 보장 됩니다 .

또한 생성자가 포인터를 사용하기 때문에 사용자가 A전달하는 객체의 수명에주의를 기울여야한다는 것이 사용자에게 더 분명합니다 .


관련 주제는 다음과 같습니다.


20

이 관용구를 설명하는 이름이 있습니까?

이 사용법에 대한 이름은 없으며 단순히 "Reference as class member"라고 합니다.

나는 그것이 크고 복잡한 객체를 복사하는 데 따르는 큰 오버 헤드를 방지하는 것이라고 가정하고 있습니다.

예, 한 개체의 수명을 다른 개체와 연결하려는 시나리오도 있습니다.

이것은 일반적으로 좋은 습관입니까? 이 접근 방식에 함정이 있습니까?

사용량에 따라 다릅니다. 모든 언어 기능을 사용하는 것은 "강좌를위한 말 선택" 과 같습니다 . 모든 ( 거의 모든 ) 언어 기능이 일부 시나리오에서 유용하기 때문에 존재 한다는 점에 유의하는 것이 중요합니다 .
참조를 클래스 멤버로 사용할 때 유의해야 할 몇 가지 중요한 사항이 있습니다.

  • 참조 된 객체가 클래스 객체가 존재할 때까지 존재하도록 보장해야합니다.
  • 생성자 멤버 이니셜 라이저 목록에서 멤버를 초기화해야합니다. 포인터 멤버의 경우 가능할 수 있는 지연 초기화를 가질 수 없습니다 .
  • 컴파일러는 복사 할당을 생성하지 않으며 operator=()사용자가 직접 제공해야합니다. =이러한 경우 운영자가 취해야 할 조치를 결정하는 것은 번거 롭습니다 . 따라서 기본적으로 클래스는 할당 할 수 없게됩니다 .
  • NULL다른 객체를 참조하기 위해 참조하거나 만들 수 없습니다 . 재 장착이 필요한 경우에는 포인터의 경우와 같이 참조를 사용할 수 없습니다.

가장 실용적인 목적을 위해 (멤버 크기로 인한 높은 메모리 사용을 염려하지 않는 한) 포인터 나 참조 멤버 대신 멤버 인스턴스 만 있으면 충분합니다. 이렇게하면 참조 / 포인터 멤버가 추가 메모리 사용을 희생하면서 가져 오는 다른 문제에 대해 걱정할 필요가 없습니다.

포인터를 사용해야하는 경우 원시 포인터 대신 스마트 포인터를 사용해야합니다. 그것은 포인터로 당신의 삶을 훨씬 더 쉽게 만들 것입니다.


" 나는 그것이 크고 복잡한 객체를 복사하는 데 따르는 큰 오버 헤드를 방지하기위한 것이라고 가정하고 있습니다. 예"-이 패턴이 복사를 피하는 것과 관련이 있다고 생각하는 이유를 설명해주세요. 이 비 '관용구'가 실제 목적의 부작용으로 누군가를 위해 사본을 저장한다면 원래 디자인은 치명적 결함이었으며이 패턴으로 제자리를 바꾸는 것만으로는 예상대로 작동하지 않을 것입니다 .
underscore_d

@underscore_d 클래스에 상수 데이터의 양이 적지 않고 동시에이 클래스의 인스턴스가 여러 개있을 수 있다고 가정 해 보겠습니다. 각 인스턴스가 해당 const 데이터의 자체 복사본을 갖는 것은 용납 할 수 없을 정도로 낭비 일 수 있습니다. 따라서 공유 할 수있는 해당 데이터의 외부 위치에 대한 const 참조를 저장하면 많은 복사가 절약됩니다. shared_ptr은 데이터 핸들이 반드시 동적으로 할당 될 필요가 없기 때문에 반드시 솔루션이되는 것은 아닙니다.
중요하지 않음

@Unimportant 2016 년에 제 이의가 무엇인지 잘 모르겠습니다. 저는 항상 참조를 클래스 멤버로 사용합니다. 가치에 의한 소유권을 단순히 참조로 바꾸는 것만으로도 평생의 질문으로 이어질 수 있고 반드시 만남이나 항상 1 : 1로 할 수있는 일이 아닐까 걱정했을 수도 있습니다. 몰라요.
underscore_d

1

C ++는 클래스 / 구조체를 통해 객체의 수명을 관리하는 좋은 메커니즘을 제공합니다. 이것은 다른 언어에 비해 C ++의 가장 좋은 기능 중 하나입니다.

ref 또는 포인터를 통해 노출 된 멤버 변수가 있으면 원칙적으로 캡슐화를 위반합니다. 이 관용구를 사용하면 클래스의 소비자가 A 객체에 대한 지식이나 제어 권한없이 A 객체의 상태를 변경할 수 있습니다. 또한 소비자가 A 객체의 수명을 넘어서 A의 내부 상태에 대한 참조 / 포인터를 유지할 수 있습니다. 이것은 잘못된 설계입니다. 대신 클래스는 공유 객체에 대한 참조 / 포인터를 보유하도록 리팩터링 될 수 있으며 (소유하지 않음) 생성자를 사용하여 설정할 수 있습니다 (수명 규칙 위임). 공유 객체의 클래스는 경우에 따라 멀티 스레딩 / 동시성을 지원하도록 설계 될 수 있습니다.


2
그러나 OP의 코드는 멤버 변수에 대한 참조를 보유하지 않습니다. 참조 인 멤버 변수가 있습니다. 그래서 좋은 점이지만 겉보기에는 관련이 없습니다.
underscore_d

-3

회원 참조는 일반적으로 잘못된 것으로 간주됩니다. 그들은 회원 포인터에 비해 삶을 힘들게 만듭니다. 그러나 그것은 특별히 비정상적이지도 않고 특별한 관용구 나 사물도 아닙니다. 그것은 단지 앨리어싱입니다.


23
일반적으로 나쁜 것으로 간주 되는 지원에 대한 언급을 제공 할 수 있습니까 ?
David Rodríguez-dribeas
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.