임시를 허용하지 않는 방법


107

Foo 클래스의 경우 이름을 지정하지 않고 구성하는 것을 허용하지 않는 방법이 있습니까?

예를 들면 :

Foo("hi");

다음과 같이 이름을 지정하는 경우에만 허용 하시겠습니까?

Foo my_foo("hi");

첫 번째 항목의 수명은 명령문 일 뿐이고 두 번째 항목은 둘러싸는 블록입니다. 내 사용 사례에서는 Foo생성자와 소멸자 사이의 시간을 측정하고 있습니다. 지역 변수를 참조하지 않기 때문에 종종 입력하는 것을 잊고 실수로 수명을 변경합니다. 대신 컴파일 시간 오류가 발생하고 싶습니다.


8
이것은 뮤텍스 잠금 가드에도 유용 할 수 있습니다.
lucas clemente

1
글쎄, 당신 그것이 금지 된 곳에서 당신 자신의 C ++ 컴파일러를 작성할 수 있지만, 엄밀히 말하면 그것은 C ++가 아닐 것입니다. 예를 들어 (같은 return std::string("Foo");) 함수에서 객체를 반환 할 때와 같은 임시가 유용 할 수있는 곳도 있습니다.
일부 프로그래머 친구

2
아니, 당신은, 미안이 작업을 수행 할 수 없습니다
아르 멘 Tsirunyan

2
종교에 따라 이것은 매크로가 유용 할 수있는 경우 일 수 있습니다 (항상 변수를 생성하는 매크로를 통해서만 해당 유형을 사용하여)
PlasmaHH

3
컴파일러 해킹으로 구문 적으로 방지하고 싶은 것보다 LINT 도구가 포착하기를 원하는 것 같습니다.
Warren P

답변:


101

또 다른 매크로 기반 솔루션 :

#define Foo class Foo

문이로 Foo("hi");확장되어 형식 class Foo("hi");이 잘못되었습니다. 하지만으로 Foo a("hi")확장됩니다 class Foo a("hi").

이것은 기존 (올바른) 코드와 소스 및 바이너리 호환이 가능하다는 장점이 있습니다. (이 주장은 전적으로 정확하지 않습니다. Johannes Schaub의 설명과 다음 토론을 참조하십시오. "기존 코드와 소스가 호환되는지 어떻게 알 수 있습니까? 그의 친구가 헤더를 포함하고 void f () {int Foo = 0;} 이전에 잘 컴파일되어 이제 잘못 컴파일됩니다! 또한 Foo 클래스의 멤버 함수를 정의하는 모든 행이 실패합니다. void class Foo :: bar () {} " )


51
기존 코드와 소스가 호환되는지 어떻게 알 수 있습니까? 그의 친구는 그의 헤더를 포함하고 있으며 void f() { int Foo = 0; }이전에 잘 컴파일되었고 지금은 잘못 컴파일되었습니다! 또한 Foo 클래스의 멤버 함수를 정의하는 모든 줄이 실패합니다 void class Foo::bar() {}.
Johannes Schaub-litb

21
어떻게 이렇게 많은 표를 얻을 수 있습니까? @ JohannesSchaub-litb의 댓글을 보면 이것이 정말 나쁜 해결책이라는 것을 이해할 것입니다. 이후 멤버 함수의 모든 정의가 유효하지 않기 때문에 .. -1 내 쪽
Aamir

2
@ JustMaximumPower : 그렇지 않다면 다시 나쁜 (더 나쁘게 읽음) 해결 방법이기 때문에 비꼬는 것이기를 바랍니다. 정의를 해제 한 후 다시 정사각형으로 돌아 왔기 때문에 비슷한 줄 즉, Foo("Hi")Foo.cpp 내부 에서 컴파일 오류 (OP가 의도 한 것)가 발생하지 않을 것입니다
Aamir

1
@Aamir 아니 진심이야. Martin C. Martin은 구현이 아닌 Foo의 사용을 보호하기 위해이를 사용하려고합니다.
JustMaximumPower 2011

1
Visual Studio 2012에서 시도해 보았고 class Foo("hi");컴파일해도 괜찮습니다.
fresky

71

약간의 해킹은 어떻습니까

class Foo
{
    public:
        Foo (const char*) {}
};

void Foo (float);


int main ()
{
    Foo ("hello"); // error
    class Foo a("hi"); // OK
    return 1;
}

1
그레이트 해킹! 참고 : Foo a("hi");(없이 class)도 오류입니다.
비트 마스크

이해가 잘 안됩니다. Foo ( "hello")는 void Foo (float) 호출을 시도하고 링커 오류가 발생합니까? 하지만 왜 Foo ctor 대신 float 버전이 호출됩니까?
undu

2
undu, hm 어떤 컴파일러를 사용하고 있습니까? gcc 3.4는 float 로의 변환이 없다고 불평합니다. Foo클래스보다 우선하기 때문에 함수를 호출하려고합니다 .

@aleguna 실제로 나는이 코드를 실행하려고하지 않았습니다. 그냥 (나쁜) 추측이었습니다.
undu

1
@didierc no, Foo::Foo("hi")C ++에서는 허용되지 않습니다.
Johannes Schaub-litb

44

생성자를 비공개로하지만 클래스에 생성 메서드를 제공합니다 .


9
-1 : 이것이 OP의 문제를 어떻게 해결합니까? 여전히 Foo::create();덮어 쓸 수 있습니다Foo const & x = Foo::create();
Thomas Eding 2010 년

@ThomasEding 나는 당신이 옳다고 생각합니다, 그것은 OP의 핵심 문제를 해결하지 않지만 그가 생각하고 실수를하지 않도록 강요합니다.
dchhetri

1
@ThomasEding 당신은 시스템을 깨고 싶어하는 화난 사용자로부터 자신을 보호 할 수 없습니다. @ecatmur의 해킹으로도 말할 std::common_type<Foo>::type()수 있고 일시적으로 얻을 수 있습니다 . 또는 typedef Foo bar; bar().
Johannes Schaub-litb

@ JohannesSchaub-litb :하지만 큰 차이는 그것이 실수인지 아닌지입니다. std::common_type<Foo>::type()실수로 입력하는 것은 거의 없습니다 . Foo const & x = ...우연히 밖으로 나가는 것은 완전히 믿을 수 있습니다.
Thomas Eding 2012

24

이로 인해 컴파일러 오류가 발생하지 않지만 런타임 오류가 발생합니다. 잘못된 시간을 측정하는 대신 허용 가능한 예외가 발생합니다.

보호하려는 모든 생성자 set(guard)는 호출 되는 기본 인수가 필요합니다 .

struct Guard {
  Guard()
    :guardflagp()
  { }

  ~Guard() {
    assert(guardflagp && "Forgot to call guard?");
    *guardflagp = 0;
  }

  void *set(Guard const *&guardflag) {
    if(guardflagp) {
      *guardflagp = 0;
    }

    guardflagp = &guardflag;
    *guardflagp = this;
  }

private:
  Guard const **guardflagp;
};

class Foo {
public:
  Foo(const char *arg1, Guard &&g = Guard()) 
    :guard()
  { g.set(guard); }

  ~Foo() {
    assert(!guard && "A Foo object cannot be temporary!");
  }

private:
  mutable Guard const *guard;
}; 

특성은 다음과 같습니다.

Foo f() {
  // OK (no temporary)
  Foo f1("hello");

  // may throw (may introduce a temporary on behalf of the compiler)
  Foo f2 = "hello";

  // may throw (introduces a temporary that may be optimized away
  Foo f3 = Foo("hello");

  // OK (no temporary)
  Foo f4{"hello"};

  // OK (no temporary)
  Foo f = { "hello" };

  // always throws
  Foo("hello");

  // OK (normal copy)
  return f;

  // may throw (may introduce a temporary on behalf of the compiler)
  return "hello";

  // OK (initialized temporary lives longer than its initializers)
  return { "hello" };
}

int main() {
  // OK (it's f that created the temporary in its body)
  f();

  // OK (normal copy)
  Foo g1(f());

  // OK (normal copy)
  Foo g2 = f();
}

의 경우 f2, f3및 반환 "hello"싶어하지 않을 수 있습니다. 던지는 것을 방지하기 위해를 재설정 guard하여 복사본의 소스 대신 우리를 보호하도록 하여 복사본의 소스를 임시로 허용 할 수 있습니다 . 이제 위의 포인터를 사용하는 이유도 알 수 있습니다. 이는 유연성을 제공합니다.

class Foo {
public:
  Foo(const char *arg1, Guard &&g = Guard()) 
    :guard()
  { g.set(guard); }

  Foo(Foo &&other)
    :guard(other.guard)
  {
    if(guard) {
      guard->set(guard);
    }
  }

  Foo(const Foo& other)
    :guard(other.guard)
  {
    if(guard) {
      guard->set(guard);
    }
  }

  ~Foo() {
    assert(!guard && "A Foo object cannot be temporary!");
  }

private:
  mutable Guard const *guard;
}; 

의 특성 f2, f3그리고 대한이 return "hello"항상 지금 // OK.


2
Foo f = "hello"; // may throw이 코드를 사용하지 않도록 겁을주기에 충분합니다.
Thomas Eding

4
@thomas, 생성자를 표시하고 explicit그런 코드가 더 이상 컴파일되지 않는 것이 좋습니다 . 목표는 일시적인 것을 좋아하는 것이었고, 그렇습니다. 두렵다면 복사 또는 이동 생성자에서 복사본의 소스를 임시가 아닌 것으로 설정하여 던지지 않도록 할 수 있습니다. 그래도 임시로 끝나는 경우 여러 복사본의 최종 객체 만 던질 수 있습니다.
Johannes Schaub-litb

2
맙소사. 저는 C ++ 및 C ++ 11 초보자는 아니지만 이것이 어떻게 작동하는지 이해할 수 없습니다. 몇 가지 설명을 추가해 주시겠습니까? ..
Mikhail

6
@Mikhail 같은 지점에서 파괴되는 임시 객체의 파괴 순서는 구성의 역순입니다. 호출자가 전달하는 기본 인수는 임시입니다. 경우 Foo개체가 너무 일시적이며, 기본 인수와 같은 식의 수명 종료는 다음 Foo전자가 후자 후 만들었 기 때문에 개체의 dtor은 기본 인수의 dtor 전에 호출됩니다.
Johannes Schaub-litb

1
@ JohannesSchaub-litb 아주 좋은 속임수입니다. 정말 구별하는 것은 불가능하다 생각 Foo(...);하고 Foo foo(...);내부에서 Foo.
Mikhail 2012

18

몇 년 전에 저는 그 상황에 대한 새로운 경고 옵션을 추가하는 GNU C ++ 컴파일러 용 패치 를 작성 했습니다 . 이것은 Bugzilla 항목 에서 추적됩니다 .

불행히도 GCC Bugzilla는 잘 고려 된 패치 포함 기능 제안이 사라지는 매장지입니다. :)

이는 로컬 객체를 잠금 및 잠금 해제, 실행 시간 측정 등을위한 가젯으로 사용하는 코드에서이 질문의 주제 인 버그 종류를 정확히 포착하려는 욕구에서 비롯되었습니다.


9

현재 구현에서는이 작업을 수행 할 수 없지만이 규칙을 유용하게 사용할 수 있습니다.

임시 개체는 상수가 아닌 참조에 바인딩 될 수 없습니다.

코드를 클래스에서 상수가 아닌 참조 매개 변수를 사용하는 독립 함수로 이동할 수 있습니다. 그렇게하면 임시가 상수가 아닌 참조에 바인딩하려고하면 컴파일러 오류가 발생합니다.

코드 샘플

class Foo
{
    public:
        Foo(const char* ){}
        friend void InitMethod(Foo& obj);
};

void InitMethod(Foo& obj){}

int main()
{
    Foo myVar("InitMe");
    InitMethod(myVar);    //Works

    InitMethod("InitMe"); //Does not work  
    return 0;
}

산출

prog.cpp: In function int main()’:
prog.cpp:13: error: invalid initialization of non-const reference of type Foo&’ from a temporary of type const char*’
prog.cpp:7: error: in passing argument 1 of void InitMethod(Foo&)’

1
@didierc : 그들이 추가 기능을 제공한다면, 그렇게하지 않는 것은 당신에게 달려 있습니다. 우리는 표준에서 명시 적으로 허용하지 않는 것을 달성하기위한 방법을 조정하려고 노력하고 있으므로 당연히 제한이있을 것입니다.
Alok 저장

@didierc 매개 변수 x는 명명 된 개체이므로 실제로 금지할지 여부가 명확하지 않습니다. 당신이 사용한 생성자가 명시 적이라면 사람들은 본능적으로 할 수 Foo f = Foo("hello");있습니다. 실패하면 화를 낼 것 같아요. 내 솔루션은 처음에 예외 / 어설 션 실패와 함께이를 거부했고 누군가가 불평했습니다.
Johannes Schaub-litb

@ JohannesSchaub-litb 예, OP는 바인딩을 강제하여 생성자가 생성 한 값을 버리는 것을 금지하려고합니다. 내 예가 잘못되었습니다.
didierc

7

기본 생성자가 없으며 모든 생성자에서 인스턴스에 대한 참조가 필요합니다.

#include <iostream>
using namespace std;

enum SelfRef { selfRef };

struct S
{
    S( SelfRef, S const & ) {}
};

int main()
{
    S a( selfRef, a );
}

3
좋은 생각이지만 하나의 변수가 생기면 바로 S(selfRef, a);. : /
Xeo

3
@Xeo S(SelfRef, S const& s) { assert(&s == this); }, 런타임 오류가 허용되는 경우.

6

아니요, 불가능합니다. 그러나 매크로를 생성하여 동일한 효과를 얻을 수 있습니다.

#define FOO(x) Foo _foo(x)

이것으로 Foo my_foo (x) 대신 FOO (x)를 작성할 수 있습니다.


5
나는 찬성 투표를하려고했지만 "당신은 매크로를 만들 수있다"는 것을 보았습니다.
Griwes

1
네, 밑줄을 수정했습니다. @Griwes-근본 주의자가되지 마십시오. "이 작업을 수행 할 수 없습니다"보다 "매크로 사용"이라고 말하는 것이 좋습니다.
amaurea

5
음, 할 수 없습니다. 문제를 전혀 해결하지 못했지만 여전히 완벽하게 합법적 Foo();입니다.
Puppy

11
이제 당신은 여기에서 고집을 부리고 있습니다. Foo 클래스의 이름을 복잡한 것으로 바꾸고 매크로를 Foo라고 부릅니다. 문제 해결됨.
amaurea

8
같은 것 :class Do_not_use_this_class_directly_Only_use_it_via_the_FOO_macro;
Benjamin Lindley

4

주요 목표는 버그를 방지하는 것이므로 다음을 고려하십시오.

struct Foo
{
  Foo( const char* ) { /* ... */ }
};

enum { Foo };

int main()
{
  struct Foo foo( "hi" ); // OK
  struct Foo( "hi" ); // fail
  Foo foo( "hi" ); // fail
  Foo( "hi" ); // fail
}

이렇게하면 변수 이름을 지정하는 것을 잊을 수없고 struct. 상세하지만 안전합니다.


1

단일 매개 변수 생성자를 명시 적으로 선언하면 아무도 의도하지 않게 해당 클래스의 객체를 생성하지 않습니다.

예를 들면

class Foo
{
public: 
  explicit Foo(const char*);
};

void fun(const Foo&);

이 방법으로 만 사용할 수 있습니다

void g() {
  Foo a("text");
  fun(a);
}

그러나 결코 이런 식으로 (스택의 임시를 통해)

void g() {
  fun("text");
}

참조 : Alexandrescu, C ++ Coding Standards, Item 40.


3
이것은 허용 fun(Foo("text"));합니다.
Guilherme Bernal 2012
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.