'mutable'키워드는 const 함수로 변수를 수정하는 것 이외의 다른 목적을 가지고 있습니까?


527

얼마 전에 나는 클래스 멤버 변수를 mutable키워드 로 표시하는 코드를 발견했습니다 . 내가 볼 수있는 한 단순히 const메소드 에서 변수를 수정할 수 있습니다 .

class Foo  
{  
private:  
    mutable bool done_;  
public:  
    void doSomething() const { ...; done_ = true; }  
};

이것이이 키워드의 유일한 사용입니까 아니면 눈을 맞추는 것보다 더 많은 것이 있습니까? 그 후 클래스 에서이 기술을 사용 하여 스레드 안전성 이유로 인해 함수를 잠글 boost::mutex수 있도록 변경 가능으로 표시 const했지만 솔직히 말하면 약간의 해킹처럼 느껴집니다.


2
그러나 아무것도 수정하지 않으면 왜 뮤텍스를 먼저 사용해야합니까? 나는 단지 이것을 이해하고 싶다.
Misgevolution

@ Misgevolution 당신이 무언가를 수정하고 있습니다, 당신은 const를 통해 수정을 할 수있는 사람 / 방법을 제어하고 있습니다. 정말 순진한 예입니다. 친구에게 일정하지 않은 핸들 만 주면 적들이 const 핸들을 얻는다고 상상해보십시오. 친구는 수정할 수 있고, 적은 할 수 없습니다.
iheanyi

1
참고 : 여기에 키워드 사용의 좋은 예입니다 mutable: stackoverflow.com/questions/15999123/...은
가브리엘 스테이 플스

const-by-default, 즉 (명시 적 변경 가능) 및 (암시 적 const)를 원한다면 const(유형) 을 재정의하는 데 사용할 수 있기를 바랍니다 . class A_mutable{}; using A = A_mutable const; mutable_t<A> a;mutable A a;A a;
alfC

답변:


351

비트 단위 const와 논리 const를 구분할 수 있습니다. 논리적 const는 잠금 예제와 같이 공용 인터페이스를 통해 볼 수있는 방식으로 객체가 변경되지 않는 경우입니다. 또 다른 예로는 처음 요청할 때 값을 계산하고 결과를 캐시하는 클래스가 있습니다.

c ++ 11 mutable을 람다에서 사용하여 값으로 캡처 한 항목을 수정할 수 있음을 나타냅니다 (기본적으로는 아님).

int x = 0;
auto f1 = [=]() mutable {x = 42;};  // OK
auto f2 = [=]()         {x = 42;};  // Error: a by-value capture cannot be modified in a non-mutable lambda

52
'mutable'은 비트 / 논리적 constness에 전혀 영향을 미치지 않습니다. C ++은 단지 비트 const를하고는 '가변'키워드이 검사에서 구성원을 제외 할 수 있습니다. 추상화를 통해 (예 : SmartPtrs) C ++에서 '논리적'const를 달성하는 것은 불가능합니다.
Richard Corden

111
@Richard : 당신은 요점을 놓치고 있습니다. "논리적 const"키워드는 없습니다. 사실, 이는 객체의 논리적 관찰 가능 상태를 구성하는 요소에 대한 이해를 바탕으로 프로그래머가 변경 가능하도록 멤버를 결정하도록하는 개념적 차별화입니다.
Tony Delroy

6
@ajay 네, const 변수에서 변경 될 수 있도록 멤버 변수를 변경 가능한 것으로 표시합니다.
KeithB

6
람다에서 변경이 필요한 이유는 무엇입니까? 참조로 변수를 캡처하는 것으로 충분하지 않습니까?
Giorgio

11
@Giorgio : 차이점은 x람다 내 에서 수정 된 부분 이 람다 내에 남아 있다는 것입니다. 즉, 람다 함수는 자신의 사본 만 수정할 수 있습니다 x. 변경 사항은 외부에서 볼 수 없으며 원본 x은 변경되지 않습니다. 람다는 functor 클래스로 구현됩니다. 캡처 된 변수는 멤버 변수에 해당합니다.
Sebastian Mach

138

mutable키워드는 관통하는 방법입니다 const당신이 당신의 객체를 통해 드레이프 베일을. const 참조 또는 객체에 대한 포인터가있는 경우 언제 어떻게 표시되는지를 제외하고 는 해당 객체를 수정할 수 없습니다 mutable.

당신과 함께 const참조 또는 포인터 당신이하는 제약이있다 :

  • 보이는 데이터 멤버에 대해서만 읽기 권한
  • 로 표시된 메소드 만 호출 할 수있는 권한 const.

mutable지금 작성하거나 표시된 일련의 데이터 멤버 수 있도록 예외를 만든다 mutable. 그것은 외부에서 볼 수있는 유일한 차이점입니다.

내부적 const으로 볼 수 있는 메소드는 표시된 데이터 멤버에 쓸 수도 있습니다 mutable. 본질적으로 const 베일은 포괄적으로 관통됩니다. 개념을 mutable파괴하지 않으며 const유용한 특수한 경우에만 사용 되도록하는 것은 API 디자이너의 책임입니다. mutable키워드는 이러한 특별한 경우에 적용됩니다 그것 때문에 명확하게 표시 데이터 멤버를하는 데 도움이됩니다.

실제로 const코드베이스 전체에 강박 적으로 사용할 수 있습니다 (기본적으로 const"질병"으로 코드베이스를 "감염"하려는 경우 ). 이 세계에서 포인터와 참조는 const거의 예외가 없으므로 추론하고 이해하기 쉬운 코드를 생성합니다. 흥미로운 탈선을 보려면 "참조 투명성"을 찾아보십시오.

mutable키워드가 없으면 결국 const_cast다양한 유용한 특수 사례 (캐싱, 참조 횟수 계산, 데이터 디버그 등)를 처리하는 데 사용해야 합니다. 불행히도 API 클라이언트 가 사용중인 객체 의 보호 를 강제로 const_cast파괴 mutable하기 때문에 훨씬 더 파괴적 입니다. 또한이 광범위하게 발생 파괴 : 을 가리키는 const 포인터 또는 참조를 보내고하는 것은 자유롭게 쓰기 눈에 보이는 회원에 대한 액세스를 호출하는 방법을 수 있습니다. 반대로 API 디자이너는 예외에 대해 세분화 된 제어를 수행해야 하며 일반적으로 이러한 예외는 개인 데이터 에서 작동하는 메소드에 숨겨져 있습니다.constconstconst_castmutableconstconst

(NB는 데이터와 메소드 가시성 을 몇 번 언급 합니다 . 여기에서 논의 된 완전히 다른 유형의 객체 보호 인 public 또는 private 또는 protected로 표시된 멤버에 대해 이야기하고 있습니다 .)


8
또한 객체 const_cast의 일부를 수정하는 데 사용 const하면 정의되지 않은 동작이 발생합니다.
Brian

API 클라이언트가 객체의 const 보호를 강제로 파괴하기 때문에 동의하지 않습니다 . 사용 된 경우 const_castA의 멤버 변수의 변이를 구현하는 const방법은이 캐스트를 수행하는 방법은 클라이언트를 요청하지 것이다 - 당신은 그것을 할 거라고 방법 내 에서 const_cast보내고 this. 기본적으로는에서 임의의 멤버에 const와 바이 패스를 할 수 있습니다 특정 호출 사이트 동안, mutable하자 당신이 const를 제거의 특정 구성원 모든 통화 사이트에서. 후자는 일반적으로 일반적인 용도 (캐싱, 통계)를 위해 원하는 것이지만 때로는 const_cast가 패턴에 맞습니다.
BeeOnRope

1
const_cast패턴이 더 나은 같은 임시 멤버를 수정하려는 경우와 같이 어떤 경우에 적합을 수행하고 (거의 같은 복원 boost::mutex). 최종 상태가 초기 상태와 동일하기 때문에이 방법은 논리적으로 구성되어 있지만 일시적으로 변경하려고합니다. const_cast이 방법은 돌연변이가 취소 될 수있는 방법에서 구체적으로 const를 제거 할 수 있기 때문에 유용 할 수 있지만 , 모든 방법 mutable에서 const 보호를 제거하기 때문에 적절 하지 않을 수 있습니다. , 실행 취소 "패턴입니다.
BeeOnRope

2
const로 정의 된 객체를 읽기 전용 메모리 (일반적으로 읽기 전용으로 표시된 메모리 ) 및 const_cast가능한 표준 시간대에 배치하면 시간 폭탄 을 사용할 수 있습니다 . mutable이러한 개체는 읽기 전용 메모리에 배치 할 수 없으므로 이러한 문제가 없습니다.
BeeOnRope

75

boost :: mutex와 함께 사용하는 것이 바로이 키워드를위한 것입니다. 또 다른 용도는 액세스 속도를 높이기위한 내부 결과 캐싱입니다.

기본적으로 'mutable'은 외부에서 볼 수있는 객체의 상태에 영향을 미치지 않는 클래스 속성에 적용됩니다.

귀하의 질문에있는 샘플 코드에서 done_ 값이 외부 상태에 영향을 미치면 변경 가능 변수가 부적절 할 수 있습니다. 부품.


35

가변은 const메소드 내에서 특정 속성을 수정 가능한 것으로 표시하기위한 것 입니다. 그것이 유일한 목적입니다. 사용하지 않고 디자인을 변경하면 코드가 더 깨끗하고 읽기 쉽습니다 mutable.

http://www.highprogrammer.com/alan/rants/mutable.html

위의 광기가 가변적 인 것이 아니라면 무엇입니까? 다음은 미묘한 경우입니다. 변경 가능은 객체가 논리적으로 일정하지만 실제로 변경해야하는 경우입니다. 이러한 경우는 거의 없으며 그 사이에 존재합니다.

저자가 제공하는 예에는 캐싱 및 임시 디버깅 변수가 포함됩니다.


2
이 링크는 mutable이 도움이되는 시나리오의 가장 좋은 예라고 생각합니다. 거의 디버깅에 독점적으로 사용되는 것 같습니다. (올바른 사용법에 따라)
enthusiasticgeek

를 사용 mutable하면 코드를보다 읽기 쉽고 깔끔하게 만들 수 있습니다. 다음 예에서 예상대로 될 read수 있습니다 const. `가변 m_mutex; 컨테이너 m_container; void add (Item item) {Lockguard 잠금 (m_mutex); m_container.pushback (항목); } 항목 읽기 () const {Lockguard lock (m_mutex); return m_container.first (); }`
Th. Thielemann

매우 인기있는 유스 케이스가 하나 있습니다 : 참조 횟수.
Seva Alekseyev

33

캐시와 같은 내부 상태가 숨겨진 상황에서 유용합니다. 예를 들면 다음과 같습니다.

HashTable 클래스
{
...
공공의:
    문자열 조회 (문자열 키) const
    {
        if (key == lastKey)
            lastValue를 반환;

        문자열 값 = lookupInternal (key);

        lastKey = 키;
        lastValue = 값;

        반환 값;
    }

은밀한:
    변경 가능한 문자열 lastKey, lastValue;
};

그런 다음 const HashTable개체 lookup()가 내부 캐시를 수정하는 메서드를 계속 사용할 수 있습니다 .


9

mutable 그렇지 않으면 상수 함수에서 데이터를 수정할 수 있다고 유추 할 때 존재합니다.

의도는 객체의 내부 상태에 대해 "아무것도하지 않는"함수가있을 수 있으므로 함수를 표시 const하지만 실제로는 영향을 미치지 않는 방식으로 일부 객체 상태를 수정해야 할 수도 있습니다. 기능.

키워드는 컴파일러에 대한 힌트 역할을 할 수 있습니다. 이론적 인 컴파일러는 메모리에 읽기 전용으로 표시된 상수 객체 (전역과 같은)를 배치 할 수 있습니다. 이 mutable작업을 수행하지 않아야한다는 힌트가 있습니다.

변경 가능한 데이터를 선언하고 사용하는 몇 가지 유효한 이유는 다음과 같습니다.

  • 나사산 안전. 를 선언하는 mutable boost::mutex것은 완벽합니다.
  • 통계. 인수의 일부 또는 전부가 주어지면 함수 호출 횟수를 계산합니다.
  • 메모. 값 비싼 답변을 계산 한 다음 다시 계산하지 않고 나중에 참조 할 수 있도록 저장하십시오.

2
변경 가능한 "힌트"에 관한 주석을 제외하고는 정답입니다. 따라서 컴파일러가 객체를 ROM에 배치하면 변경 가능한 멤버가 변경되지 않는 것처럼 보입니다. 변경 가능한 동작은 잘 정의되어 있습니다.
Richard Corden

2
const 객체를 읽기 전용 메모리에 배치하는 것 외에도 컴파일러는 루프에서 const fucntion 호출을 최적화하기로 결정할 수도 있습니다. 그렇지 않으면 const 함수의 변경 가능한 통계 카운터는 더 많은 호출을 계산하기 위해 최적화를 방지하는 대신 여전히 그러한 최적화를 허용하고 하나의 호출 만 계산합니다.
Hagen von Eitzen

@HagenvonEitzen-나는 그것이 틀렸다고 확신합니다. 컴파일러는 부작용이 없음을 증명할 수 없으면 루프 밖으로 함수를 끌어 올릴 수 없습니다. 그 증거는 일반적으로 실제로 기능 (가 인라인 종종 후)와에 의존하지 않는 구현 검사 포함 const(및 검사가 성공하거나에 관계없이 실패 const또는 mutable). 단순히 함수를 선언하는 것만으로 const는 충분하지 않습니다. const함수는 전역 변수를 수정하거나 함수에 전달되는 것과 같은 부작용이 생길 수 있으므로 그 증거를 보장하지는 않습니다.
BeeOnRope

이제 일부 컴파일러에는 gcc의 _attribute __ ((const)) 및 __attribute __ ((pure)) 와 같은 특수 확장이 있습니다.이 속성은 _do 와 같은 영향을 미치지constC ++ 의 키워드 와 접하게 관련이 있습니다.
BeeOnRope

8

그래, 그게 그거야. 캐시를 구현하여 조회 속도를 높이기 위해 클래스의 상태를 논리적으로 변경 하지 않는 메서드로 수정 된 멤버에 사용합니다 .

class CIniWrapper
{
public:
   CIniWrapper(LPCTSTR szIniFile);

   // non-const: logically modifies the state of the object
   void SetValue(LPCTSTR szName, LPCTSTR szValue);

   // const: does not logically change the object
   LPCTSTR GetValue(LPCTSTR szName, LPCTSTR szDefaultValue) const;

   // ...

private:
   // cache, avoids going to disk when a named value is retrieved multiple times
   // does not logically change the public interface, so declared mutable
   // so that it can be used by the const GetValue() method
   mutable std::map<string, string> m_mapNameToValue;
};

이제는주의해서 사용해야합니다. 호출자는 const메소드 만 사용 하는 경우 스레드 안전하다고 가정 할 수 있으므로 동시성 문제는 큰 관심사 입니다. 물론 mutable데이터를 수정 해도 개체의 동작이 중요한 방식으로 변경되어서는 안됩니다. 예를 들어 디스크에 작성된 변경 사항이 앱에 즉시 표시 될 것으로 예상되는 경우 내가 제공 한 예를 위반할 수 있습니다 .


6

Mutable은 클래스 내부에 뮤텍스 또는 잠금과 같은 것을 신호하기 위해 해당 클래스 내에서만 사용되는 변수가있을 때 사용됩니다. 이 변수는 클래스의 동작을 변경하지 않지만 클래스 자체의 스레드 안전성을 구현하기 위해 필요합니다. 따라서 "mutable"이 없으면 "const"함수를 사용할 수 없습니다.이 변수는 외부에서 사용할 수있는 모든 함수에서 변경해야하기 때문입니다. 따라서 const 함수에서도 멤버 변수를 쓰기 가능하도록 만들기 위해 mutable이 도입되었습니다.

지정된 변경 가능 변수는 컴파일러와 판독기 모두에게 안전하고 멤버 변수가 const 멤버 함수 내에서 수정 될 수 있음을 알립니다.


4

mutable은 주로 클래스의 구현 세부 사항에 사용됩니다. 클래스의 사용자는 그것에 대해 알 필요가 없으므로 "메소드"가 const가 될 수 있다고 생각하는 메소드입니다. 뮤텍스를 변경할 수있는 예는 좋은 표준 예입니다.


4

C ++의 많은 것들과 마찬가지로, 변경 가능도 게으른 프로그래머에게 해킹 될 있습니다.


3

논리적으로 사용자에게 상태가없는 (공개 클래스의 API에 "const"게터가 있어야하지만) 기본 구현 (.cpp의 코드)에는 상태가없는 경우 "mutable"을 사용하십시오.

내가 가장 자주 사용하는 경우는 상태가없는 "일반 오래된 데이터"멤버의 초기화 지연입니다. 즉, 이러한 구성원이 빌드 (프로세서) 또는 운반 (메모리) 비용이 많이 들고 개체의 많은 사용자가 요청하지 않는 좁은 경우에 이상적입니다. 이러한 상황에서는 성능을 위해 백엔드에서 게으른 구성을 원합니다. 빌드 된 오브젝트의 90 %가 오브젝트를 전혀 빌드 할 필요가 없지만, 공용 소비를위한 올바른 Stateless API를 제시해야합니다.


2

가변은의 의미를 바꾼다 const 은 클래스의 비트 단위 const에서 논리 const로 .

이는 변경 가능한 멤버가있는 클래스는 더 이상 비트 단위이며 더 이상 실행 파일의 읽기 전용 섹션에 나타나지 않음을 의미합니다.

또한 const멤버 함수가을 사용하지 않고 변경 가능한 멤버를 변경할 수 있도록하여 유형 확인을 수정합니다 const_cast.

class Logical {
    mutable int var;

public:
    Logical(): var(0) {}
    void set(int x) const { var = x; }
};

class Bitwise {
    int var;

public:
    Bitwise(): var(0) {}
    void set(int x) const {
        const_cast<Bitwise*>(this)->var = x;
    }
};

const Logical logical; // Not put in read-only.
const Bitwise bitwise; // Likely put in read-only.

int main(void)
{
    logical.set(5); // Well defined.
    bitwise.set(5); // Undefined.
}

자세한 내용은 다른 답변을 참조하십시오. 단지 유형에 대한 것이 아니라 컴파일 된 결과에 영향을 미친다는 점을 강조하고 싶습니다.


1

잘못 설계된 반복자와 같은 경우에 따라 클래스의 수 또는 기타 부수적 인 값을 유지해야하며 이는 클래스의 주요 "상태"에 실제로 영향을 미치지 않습니다. 이것은 가장 자주 변하기 쉬워 사용되는 곳입니다. 변경 가능하지 않으면 디자인의 전체 구성 요소를 희생해야합니다.

그것은 대부분 나에게 해킹처럼 느껴집니다. 매우 적은 상황에서 유용합니다.


1

고전적인 예 (다른 답변에서 언급했듯이)와 mutable지금까지 사용 된 키워드를 본 유일한 상황 은 복잡한 결과를 캐싱하는 것입니다.Get 은 캐시가 클래스의 데이터 멤버로 구현되고 메소드 . 메소드의 정적 변수 (여러 함수 또는 일반 청결도 사이의 공유로 인해).

일반적으로 mutable키워드 사용에 대한 대안 은 일반적으로 메소드 또는 const_cast트릭 에서 정적 변수 입니다.

다른 자세한 설명은 여기 있습니다 .


1
가변 멤버에 대한 일반적인 대안으로 정적 멤버를 사용하는 것에 대해 들어 본 적이 없습니다. 그리고 const_cast당신이 경우에만 해당됩니다 알고있는 무언가가 변경되지 않습니다 (또는 보장 된) (예를 들어, C 라이브러리를 방해하는 경우) 당신이 때, 또는 알고 는 const를 선언하지 않았다. 즉, const-casted const 변수를 수정하면 정의되지 않은 동작이 발생합니다.
Sebastian Mach

1
@phresnel "정적 변수"란 메서드에서 정적 자동 변수를 의미했습니다 (통화간에 유지됨). 그리고 메소드 const_cast에서 클래스 멤버를 수정하는 데 사용할 수 있습니다 const. 이것은 제가 언급 한 것입니다.
Daniel Hershcovich

1
당신이 "일반적으로"썼다는 것은 나에게 분명 const_cast하지 않았다 const. 예 const Frob f; f.something();void something() const { const_cast<int&>(m_foo) = 2;정의되지 않은 동작이 발생합니다.
Sebastian Mach

1

const 가상 함수를 재정의하고 해당 함수에서 자식 클래스 멤버 변수를 수정하려는 경우 변경 가능 기능이 유용 할 수 있습니다. 대부분의 경우 기본 클래스의 인터페이스를 변경하지 않으려는 경우 자신 만의 가변 멤버 변수를 사용해야합니다.


1

mutable 키워드는 클래스 테스트 목적으로 스텁을 작성할 때 매우 유용합니다. const 함수를 스텁 할 수 있으며 스텁에 추가 한 테스트 기능이나 카운터를 늘릴 수 있습니다 (변경 가능). 이것은 스텁 된 클래스의 인터페이스를 그대로 유지합니다.


0

우리가 가변을 사용하는 가장 좋은 예 중 하나는 딥 카피입니다. 복사 생성자에서 const &obj인수로 보냅니다 . 따라서 생성 된 새 객체는 상수 유형입니다. 새로 생성 된 const 객체의 멤버를 변경하려면 (대부분 변경하지 않지만 드물게 변경 될 수 있음)이를로 선언해야합니다 mutable.

mutable스토리지 클래스는 클래스의 비 정적 비 const 데이터 멤버에서만 사용할 수 있습니다. const로 선언 된 객체의 일부인 경우에도 클래스의 가변 데이터 멤버를 수정할 수 있습니다.

class Test
{
public:
    Test(): x(1), y(1) {};
    mutable int x;
    int y;
};

int main()
{
    const Test object;
    object.x = 123;
    //object.y = 123;
    /* 
    * The above line if uncommented, will create compilation error.
    */   

    cout<< "X:"<< object.x << ", Y:" << object.y;
    return 0;
}

Output:-
X:123, Y:1

위의 예에서 xconst로 선언 된 객체의 일부이지만 멤버 변수의 값을 변경할 수 있습니다 . 변수 x가 변경 가능한 것으로 선언 되었기 때문 입니다. 그러나 멤버 변수 값을 수정하려고하면 y컴파일러에서 오류가 발생합니다.


-1

'mutable'이라는 키워드는 실제로는 예약 된 키워드이며, 종종 상수 변수의 값을 변경하는 데 사용됩니다. constsnt의 여러 값을 원할 경우 키워드 mutable을 사용하십시오.

//Prototype 
class tag_name{
                :
                :
                mutable var_name;
                :
                :
               };   
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.