C ++의 숨겨진 기능? [닫은]


114

질문의 "숨겨진 기능"과 관련하여 C ++를 좋아하지 않습니까? 나는 그것을 밖으로 버릴 것이라고 생각했다. C ++의 숨겨진 기능은 무엇입니까?


@Devtron-기능으로 판매되는 멋진 버그 (예 : 예기치 않은 동작)를 보았습니다. 사실, 게임 산업은 실제로 이런 일이 일어나도록 노력하고 있으며이를 "긴급 게임 플레이"라고 부릅니다 (또한 Psi-Ops의 "TK Surfing"도 확인하세요. 순전히 버그 였지만 그대로 두었습니다. 게임 IMHO의 최고의 기능)
Grant Peters

5
@Laith J : 786 페이지의 ISO C ++ 표준을 처음부터 끝까지 읽은 사람은 많지 않습니다.하지만 여러분은 이미 읽었을 것입니다.
j_random_hacker

2
@Laith, @j_random : "나는 그것을 인식 어떻게, 프로그래머의 농담을하고, 무엇을 적절한 응답 무엇인가"에 내 질문을 참조하십시오 stackoverflow.com/questions/1/you-have-been-link-rolled을 .

답변:


308

대부분의 C ++ 프로그래머는 삼항 연산자에 익숙합니다.

x = (y < 0) ? 10 : 20;

그러나 그들은 그것이 lvalue로 사용될 수 있다는 것을 깨닫지 못합니다.

(a == 0 ? a : b) = 1;

약어입니다

if (a == 0)
    a = 1;
else
    b = 1;

주의해서 사용하십시오 :-)


11
매우 흥미로운. 그래도 읽을 수없는 코드를 만드는 것을 볼 수 있습니다.
Jason Baker

112
Yikes. (a == 0? a : b) = (y <0? 10:20);
Jasper Bekkers

52
(b? trueCount : falseCount) ++
Pavel Radzivilovsky

12
Dunno가 GCC에 특화된 경우, 이것도 효과가 있다는 사실에 놀랐습니다 (value ? function1 : function2)().
Chris Burt-Brown

3
@Chris Burt-Brown : 아니요, 동일한 유형 (즉, 기본 인수가 없음)이 function1있고 function2암시 적으로 함수 포인터로 변환되고 결과가 암시 적으로 다시 변환 되면 모든 곳에서 작동 합니다.
MSalters

238

오류없이 URI를 C ++ 소스에 넣을 수 있습니다. 예를 들면 :

void foo() {
    http://stackoverflow.com/
    int bar = 4;

    ...
}

41
그러나 기능 당 하나만 의심됩니다. :)
Constantin

51
@jpoh : http 다음에 콜론이 오면 나중에 goto 문에서 사용하는 "라벨"이됩니다. 위의 예에서 어떤 goto 문에도 사용되지 않았기 때문에 컴파일러에서 경고를받습니다.
utku_karatas

9
프로토콜이 다른 한 둘 이상을 추가 할 수 있습니다! ftp.microsoft.com gopher : //aerv.nl/1 등 ...
Daniel Earwicker

4
@Pavel : 식별자 뒤에 콜론이 오는 레이블은 gotoC ++에 있는와 함께 사용하기위한 것입니다 . 두 개의 슬래시 뒤에 오는 것은 주석입니다. 따라서,와 http://stackoverflow.com, http라벨은 (당신이 이론적으로 쓸 수있다 goto http;), 그리고 //stackoverflow.com그냥 끝 (end-of-line) 주석입니다. 둘 다 합법적 인 C ++이므로 구문이 컴파일됩니다. 물론 막연하게 유용한 것은 없습니다.
David Thornley

8
불행히도 goto http;실제로 URL을 따르지 않습니다. :(
Yakov Galka 2011-08-02

140

포인터 산술.

C ++ 프로그래머는 발생할 수있는 버그 때문에 포인터를 피하는 것을 선호합니다.

내가 본 것 중 가장 멋진 C ++? 아날로그 리터럴.


11
버그 때문에 포인터를 피합니까? 포인터는 기본적으로 동적 C ++ 코딩의 모든 것입니다!
Nick Bedford

1
아날로그 리터럴은 난독 화 된 C ++ 컨테스트 항목, 특히 ASCII 아트 유형에 적합합니다.
Synetech

119

대부분의 게시물에 동의합니다. C ++는 다중 패러다임 언어이므로 찾을 수있는 "숨겨진"기능 (모든 대가를 치르는 "정의되지 않은 동작"제외)은 시설의 현명한 사용입니다.

이러한 기능의 대부분은 언어의 내장 기능이 아니라 라이브러리 기반 기능입니다.

가장 중요한 것은 RAII 인데, C 세계에서 온 C ++ 개발자들이 수년 동안 무시하는 경우가 많습니다. 연산자 오버로딩 은 종종 배열과 같은 동작 (아래 첨자 연산자), 포인터와 같은 작업 (스마트 포인터) 및 내장형 작업 (행렬 곱하기)을 모두 가능하게하는 오해 된 기능입니다.

예외 의 사용 은 종종 어렵지만 일부 작업을 수행하면 예외 안전 사양 (실패하지 않는 코드 또는 성공할 커밋과 유사한 기능을 포함하는 코드 포함)을 통해 정말 강력한 코드를 생성 할 수 있습니다. 원래 상태).

C ++의 가장 유명한 "숨겨진"기능은 템플릿 메타 프로그래밍 입니다. 이는 런타임 대신 컴파일 시간에 프로그램을 부분적으로 (또는 전체적으로) 실행할 수있게 해주기 때문입니다. 그러나 이것은 어렵고 템플릿을 시도하기 전에 확실한 이해가 있어야합니다.

다른 사람들은 C ++의 조상, 즉 C 외부에서 "프로그래밍 방법"을 생성하기 위해 다중 패러다임을 사용합니다.

functors 를 사용 하면 추가 유형 안전성 및 상태 저장 기능 을 사용하여 함수를 시뮬레이션 할 수 있습니다. 명령 패턴을 사용하여 코드 실행을 지연 할 수 있습니다. 대부분의 다른 디자인 패턴 은 "공식 C ++ 패러다임"목록에 포함되지 않아야하는 대체 코딩 스타일을 생성하기 위해 C ++로 쉽고 효율적으로 구현할 수 있습니다.

템플릿 을 사용 하면 처음에 생각했던 유형을 포함하여 대부분의 유형에서 작동하는 코드를 생성 할 수 있습니다. 유형 안전성도 높일 수 있습니다 (자동화 된 typesafe malloc / realloc / free처럼). C ++ 객체 기능은 정말 강력하지만 (따라서 부주의하게 사용하면 위험합니다) 동적 다형성 도 C ++에서 정적 버전 인 CRTP를 갖습니다 .

나는 Scott Meyers의 대부분의 " Effective C ++ "유형의 책이나 Herb Sutter의 " Exceptional C ++ "유형의 책은 읽기 쉽고 C ++의 알려진 기능과 덜 알려진 기능에 대한 정보의 귀중함을 발견했습니다.

내가 선호하는 것 중 하나는 자바 프로그래머의 머리를 공포에서 벗어나게 만드는 것입니다. C ++에서 객체에 기능을 추가하는 가장 객체 지향적 인 방법은 멤버가 아닌 멤버가 아닌 친구 함수를 사용하는 것입니다. 함수 (즉, 클래스 메서드)는 다음과 같은 이유 때문입니다.

  • C ++에서 클래스의 인터페이스는 동일한 네임 스페이스의 멤버 함수이자 비 멤버 함수입니다.

  • 친구가 아닌 비회원 함수는 내부 클래스에 대한 권한있는 액세스 권한이 없습니다. 따라서 멤버가 아닌 친구보다 멤버 함수를 사용하면 클래스의 캡슐화가 약화됩니다.

이것은 경험 많은 개발자조차도 놀라지 않습니다.

(출처 : Herb Sutter의 온라인 Guru of the Week # 84 : http://www.gotw.ca/gotw/084.htm )


+1 매우 철저한 답변. 명백한 이유 때문에 불완전합니다 (그렇지 않으면 더 이상 "숨겨진 기능"이 없을 것입니다!). : p 대답 끝의 첫 번째 지점에서 클래스 인터페이스의 멤버를 언급했습니다. ".. 멤버 함수이자 친구 비 멤버 함수입니다"라는 뜻입니까?
wilhelmtell


1에 대해 언급하는 것은 koenig 조회 여야합니다. 맞죠?
Özgür

1
@wilhelmtell : 안돼 안돼 안돼 ... :-p ... "멤버 기능과 비-친구 비회원 기능"을 의미합니다 .... Koenig의 Lookup은 이러한 기능이 다른 기능보다 빨리 고려되도록 할 것입니다. " 외부 "기능을 사용하여 기호 검색
paercebal

7
아주 소수의 사람들이 깨닫는 멋진 게시물, 특히 마지막 부분에 대한 +1. 아마도 Boost 라이브러리를 "숨겨진 기능"으로 추가 할 것입니다. 나는 그것을 C ++이 가지고 있어야하는 표준 라이브러리라고 생각한다. ;)
jalf

118

학교에서 내내 들어 본 적이 없었기 때문에 다소 숨겨져 있다고 생각하는 언어 기능 중 하나는 네임 스페이스 별칭입니다. 부스트 문서에서 예제를 볼 때까지 내 관심을 끌지 못했습니다. 물론, 이제는 표준 C ++ 참조에서 찾을 수 있습니다.

namespace fs = boost::filesystem;

fs::path myPath( strPath, fs::native );

1
사용하지 않으려는 경우 유용하다고 생각합니다 using.
Siqi Lin 2010-08-20

4
또한 스레드 안전과 비 스레드 안전 또는 버전 1 대 2를 선택하든 구현간에 전환하는 방법으로도 유용합니다.
Tony Delroy 2011 년

3
네임 스페이스 계층이 큰 대규모 프로젝트에서 작업하고 헤더가 네임 스페이스 오염을 일으키지 않도록하는 경우 (그리고 변수 선언을 사람이 읽을 수 있도록하려는 경우) 특히 유용합니다.
Brandon Bohrer 2011 년

102

for루프 의 init 부분에서 변수를 선언 할 수있을 뿐만 아니라 클래스와 함수도 선언 할 수 있습니다 .

for(struct { int a; float b; } loop = { 1, 2 }; ...; ...) {
    ...
}

이는 유형이 다른 여러 변수를 허용합니다.


31
당신이 할 수 있다는 것을 알게되어 기쁩니다.하지만 개인적으로 저는 그런 일을하지 않으려 고합니다. 대부분 읽기가 어렵 기 때문입니다.
Zoomulator

2
실제로이 컨텍스트에서 작동하는 것은 쌍을 사용하는 것입니다. for (std :: pair <int, float> loop = std :: make_pair (1,2); loop.first> 0; loop.second + = 1)
Valentin Heinitz 2010

2
@Valentin 그럼 숨겨진 기능을 반대하는 대신 VS2008에 대한 버그 보고서를 작성하는 것이 좋습니다. 그것은 분명히 컴파일러의 잘못입니다.
Johannes Schaub-litb

2
흠, msvc10에서도 작동하지 않습니다. 얼마나 슬픈 지 :(
avakar

2
@avakar 사실, gcc는 v4.6에서도 거부하는 버그를 도입했습니다. :) gcc.gnu.org/bugzilla/show_bug.cgi?id=46791
Johannes Schaub-litb

77

배열 연산자는 연관 적입니다.

A [8]은 * (A + 8)의 동의어입니다. 덧셈은 연관성이므로 * (8 + A)로 다시 쓸 수 있습니다. 이것은 ....... 8 [A]의 동의어입니다.

유용하다고 말하지 않았는데 ... :-)


15
사실,이 트릭을 사용할 때는 어떤 유형을 사용하는지에주의를 기울여야합니다. A [8]은 실제로 8 번째 A이고 8 [A]는 주소 8에서 시작하는 A 번째 정수입니다. A가 바이트이면 버그가 있습니다.
Vincent Robert

38
"연관 적"이라고 말하는 곳에 "교환 적"을 의미합니까?
DarenW

28
빈센트, 틀렸어. 유형 A은 전혀 중요하지 않습니다. 예를 들어, A을했다 char*, 코드는 여전히 유효 할 것이다.
Konrad Rudolph

11
A는 반드시 포인터 여야하며 operator []를 오버로딩하는 클래스가 아니어야합니다.
David Rodríguez-dribeas

15
Vincent, 여기에는 하나의 정수 유형과 하나의 포인터 유형이 있어야하며 C도 C ++도 어느 것이 먼저 가는지 신경 쓰지 않습니다.
David Thornley

73

잘 알려지지 않은 한 가지는 공용체도 템플릿이 될 수 있다는 것입니다.

template<typename From, typename To>
union union_cast {
    From from;
    To   to;

    union_cast(From from)
        :from(from) { }

    To getTo() const { return to; }
};

그리고 생성자와 멤버 함수도 가질 수 있습니다. 상속 (가상 함수 포함)과 관련된 것은 없습니다.


흥미 롭군요! 그렇다면 모든 구성원을 초기화해야합니까? 마지막 멤버가 이전 멤버 "위에"초기화된다는 것을 의미하는 일반적인 구조체 순서를 따르는가?
j_random_hacker

j_random_hacker 오, 맞습니다. 좋은 캐치. 나는 그것이 구조체가 될 것이라고 썼다. 내가 그것을 고칠 것이다 기다립니다
요하네스 SCHAUB을 - litb

이것은 정의되지 않은 동작을 호출하지 않습니까?
Greg Bacon

7
경우 @gbacon, 그래 그것은 정의되지 않은 동작 된 invoke 수행 From하고 To그에 따라 설정하고 사용된다. 이러한 공용체는 정의 된 동작과 함께 사용할 수 있습니다 ( To부호없는 문자의 배열 또는 초기 시퀀스를 공유하는 구조체 From). 정의되지 않은 방식으로 사용하더라도 낮은 수준의 작업에는 여전히 유용 할 수 있습니다. 어쨌든, 이것은 유니온 템플릿의 한 예일뿐입니다. 템플릿 화 된 유니온에 다른 용도가있을 수 있습니다.
Johannes Schaub-litb

3
생성자에주의하십시오. 첫 번째 요소 만 생성해야하며 C ++ 0x에서만 허용됩니다. 현재 표준에서는 사소하게 구성 가능한 유형을 고수해야합니다. 그리고 소멸자는 없습니다.
Potatoswatter

72

C ++는 표준이며 숨겨진 기능이 없어야합니다.

C ++는 다중 패러다임 언어이므로 숨겨진 기능에 마지막 돈을 걸 수 있습니다. 여러 가지 중 한 가지 예 : 템플릿 메타 프로그래밍 . 표준위원회의 어느 누구도 컴파일 타임에 실행되는 Turing-complete 하위 언어를 의도하지 않았습니다.


65

C에서 작동하지 않는 또 다른 숨겨진 기능은 단항 +연산자 의 기능입니다 . 모든 종류의 것을 홍보하고 부패시키는 데 사용할 수 있습니다.

열거 형을 정수로 변환

+AnEnumeratorValue

그리고 이전에 열거 유형이 있던 열거 자 값은 이제 해당 값에 맞는 완벽한 정수 유형을 갖습니다. 수동으로 그 유형을 거의 알 수 없습니다! 예를 들어 열거 형에 대해 오버로드 된 연산자를 구현하려는 경우에 필요합니다.

변수에서 값 가져 오기

out of class 정의없이 in-class static initializer를 사용하는 클래스를 사용해야하지만 때때로 링크에 실패합니까? 연산자는 유형에 대한 assumptin 또는 종속성을 만들지 않고 임시를 만드는 데 도움이 될 수 있습니다.

struct Foo {
  static int const value = 42;
};

// This does something interesting...
template<typename T>
void f(T const&);

int main() {
  // fails to link - tries to get the address of "Foo::value"!
  f(Foo::value);

  // works - pass a temporary value
  f(+Foo::value);
}

포인터에 대한 배열 붕괴

함수에 두 개의 포인터를 전달하고 싶지만 작동하지 않습니까? 운영자가 도움을 줄 수 있습니다.

// This does something interesting...
template<typename T>
void f(T const& a, T const& b);

int main() {
  int a[2];
  int b[3];
  f(a, b); // won't work! different values for "T"!
  f(+a, +b); // works! T is "int*" both time
}

61

const 참조에 바인딩 된 임시의 수명은 거의 아는 사람이 거의 없습니다. 또는 적어도 대부분의 사람들이 알지 못하는 C ++ 지식 중 제가 가장 좋아하는 부분입니다.

const MyClass& x = MyClass(); // temporary exists as long as x is in scope

3
자세히 설명해 주시겠습니까? 당신은 그냥 놀리는 것입니다;)
Joseph Garvin 2009-06-21

8
ScopeGuard ( ddj.com/cpp/184403758 )는이 기능을 활용하는 좋은 예입니다.
MSN

2
저는 Joseph Garvin과 함께 있습니다. 우리를 깨달으십시오.
Peter Mortensen

방금 댓글에서했습니다. 게다가 const 참조 매개 변수를 사용하는 것은 자연스러운 결과입니다.
MSN


52

자주 사용되지 않는 멋진 기능은 함수 전체의 try-catch 블록입니다.

int Function()
try
{
   // do something here
   return 42;
}
catch(...)
{
   return -1;
}

주요 용도는 예외를 다른 예외 클래스로 변환하고 다시 던지거나 예외와 반환 기반 오류 코드 처리간에 변환하는 것입니다.


나는 당신 return이 Function Try의 catch 블록에서 할 수 있다고 생각하지 않습니다 .
Constantin

위의 컴파일을 시도했지만 경고가 없었습니다. 위의 예가 작동한다고 생각합니다.
vividos

7
반환은 생성자에게만 금지됩니다. 생성자의 함수 try 블록은 기본 및 멤버를 초기화하는 오류를 포착합니다 (함수 try 블록이 함수 내부에서 시도를하는 것과 다른 작업을 수행하는 유일한 경우). 다시 던지지 않으면 불완전한 개체가 생성됩니다.
puetzk

예. 이것은 매우 유용합니다. 예외를 포착하고 HRESULTS를 반환하기 위해 BEGIN_COM_METHOD 및 END_COM_METHOD 매크로를 작성하여 COM 인터페이스를 구현하는 클래스에서 예외가 유출되지 않도록했습니다. 잘 작동했습니다.
Scott Langham

3
@puetzk가 지적했듯이 이것은 기본 클래스의 생성자 또는 데이터 멤버의 생성자와 같은 생성자의 이니셜 라이저 목록 에있는 모든 항목 에서 발생한 예외를 처리하는 유일한 방법 입니다.
anton.burger 2010-06-23

44

identity/ id메타 함수 에 대해 많은 사람들이 알고 있지만 템플릿이 아닌 경우에 대한 좋은 사용 사례가 있습니다. 선언 작성 용이 :

// void (*f)(); // same
id<void()>::type *f;

// void (*f(void(*p)()))(int); // same
id<void(int)>::type *f(id<void()>::type *p);

// int (*p)[2] = new int[10][2]; // same
id<int[2]>::type *p = new int[10][2];

// void (C::*p)(int) = 0; // same
id<void(int)>::type C::*p = 0;

C ++ 선언을 해독하는 데 큰 도움이됩니다!

// boost::identity is pretty much the same
template<typename T> 
struct id { typedef T type; };

흥미롭지 만 처음에는 이러한 정의 중 일부를 읽는 데 더 많은 어려움 이있었습니다 . C ++ 선언의 내부 문제를 해결하는 또 다른 방법은 템플릿 유형 별칭을 작성하는 것입니다 template<typename Ret,typename... Args> using function = Ret (Args...); template<typename T> using pointer = *T;.-> pointer<function<void,int>> f(pointer<function<void,void>>);또는 pointer<void(int)> f(pointer<void()>);또는function<pointer<function<void,int>>,pointer<function<void,void>>> f;
bames53

42

매우 숨겨진 기능은 if 조건 내에서 변수를 정의 할 수 있으며 해당 범위는 if 및 else 블록에만 적용된다는 것입니다.

if(int * p = getPointer()) {
    // do something
}

일부 매크로는이를 사용하여 예를 들어 다음과 같이 "잠긴"범위를 제공합니다.

struct MutexLocker { 
    MutexLocker(Mutex&);
    ~MutexLocker(); 
    operator bool() const { return false; } 
private:
    Mutex &m;
};

#define locked(mutex) if(MutexLocker const& lock = MutexLocker(mutex)) {} else 

void someCriticalPath() {
    locked(myLocker) { /* ... */ }
}

또한 BOOST_FOREACH는 내부적으로 그것을 사용합니다. 이를 완료하려면 if뿐만 아니라 스위치에서도 가능합니다.

switch(int value = getIt()) {
    // ...
}

그리고 while 루프에서 :

while(SomeThing t = getSomeThing()) {
    // ...
}

(또한 for 조건). 그러나 이것이 모두 유용한 지 확실하지 않습니다. :)


산뜻한! 나는 당신이 그렇게 할 수 있다는 것을 결코 몰랐습니다 ... 오류 반환 값으로 코드를 작성할 때 약간의 번거 로움을 덜어 줄 것입니다. 이 형식에서! = 0 대신 조건부를 가질 수있는 방법이 있습니까? if ((int r = func ()) <0) 작동하지 않는 것 같습니다 ...
puetzk

puetzk, 없습니다. 하지만 다행이 :) 좋아
요하네스 SCHAUB - litb

4
@Frerich, 이것은 C 코드에서는 전혀 불가능합니다. 나는 당신이 생각하고 있다고 생각 if((a = f()) == b) ...하지만이 대답은 실제로 조건에서 변수를 선언합니다.
Johannes Schaub-litb

1
@Angry는 변수 선언이 부울 값에 대해 즉시 테스트되기 때문에 매우 다릅니다. for 루프에 대한 매핑도 있습니다. 이것은 true 인 for(...; int i = foo(); ) ...;한 본문을 통과하여 i매번 다시 초기화합니다. 당신이 보여주는 루프는 단순히 변수 선언을 보여 주지만 동시에 조건으로 작용하는 변수 선언은 아닙니다. :)
Johannes Schaub-litb

5
이 기능의 의도 된 용도가 동적 포인터 캐스트를위한 것이라고 언급하지 않았다는 점을 제외하면 매우 좋습니다.
mmocny 2011 년

29

쉼표 연산자가 연산자 오버로드를 호출하지 못하도록 방지

때로는 쉼표 연산자를 유효하게 사용하지만 사용자 정의 쉼표 연산자가 방해가되지 않도록하고 싶을 때가 있습니다. 동작. 이것이 void()게임에 등장하는 곳 입니다.

for(T i, j; can_continue(i, j); ++i, void(), ++j)
  do_code(i, j);

조건과 코드에 대한 자리 표시자를 무시하십시오. 중요한 것은 void()컴파일러가 내장 쉼표 연산자를 사용하도록 만드는입니다. 이것은 때때로 트레이 트 클래스를 구현할 때도 유용 할 수 있습니다.


나는 이것을 사용하여 과잉 표현 무시자를 마무리했습니다 . :)
GManNickG

28

생성자의 배열 초기화. 예를 들어 다음과 int같은 배열이있는 클래스에서 :

class clName
{
  clName();
  int a[10];
};

생성자에서 배열의 모든 요소를 ​​기본값 (여기서는 배열의 모든 요소를 ​​0으로)으로 초기화 할 수 있습니다.

clName::clName() : a()
{
}

6
어디서나 모든 어레이로이 작업을 수행 할 수 있습니다.
Potatoswatter

@Potatoswatter : 가장 짜증나는 구문 분석으로 인해보기보다 어렵습니다. 나는 그것이 제외 할 수있는 다른 곳을 생각할 수 없다 어쩌면 반환 값
오리 음매을

배열의 유형이 클래스 유형이면 필요하지 않습니까?
Thomas Eding 2012

27

오, 대신 애완 동물 증오 목록을 만들 수 있습니다.

  • 다형성으로 사용하려는 경우 소멸자는 가상이어야합니다.
  • 때때로 멤버는 기본적으로 초기화되지만 때로는 초기화되지 않습니다.
  • 로컬 클래스는 템플릿 매개 변수로 사용할 수 없습니다 (유용성이 떨어짐)
  • 예외 지정자 : 유용 해 보이지만 그렇지 않습니다.
  • 함수 오버로드는 서명이 다른 기본 클래스 함수를 숨 깁니다.
  • 국제화에 대한 유용한 표준화가 없습니다 (포터블 표준 와이드 문자 세트, 누구? C ++ 0x까지 기다려야합니다)

긍정적 인 측면에서는

  • 숨겨진 기능 : 기능 시도 블록. 불행히도 나는 그것을 사용하지 못했습니다. 예, 왜 추가했는지 알지만 생성자에서 다시 던져야 무의미합니다.
  • 컨테이너 수정 후 반복기 유효성에 대한 STL 보장을주의 깊게 살펴보면 약간 더 멋진 루프를 만들 수 있습니다.
  • 부스트-비밀은 아니지만 사용할 가치가 있습니다.
  • 반환 값 최적화 (명백하지는 않지만 표준에서 특별히 허용됨)
  • Functors aka function objects aka operator (). 이것은 STL에서 광범위하게 사용됩니다. 실제로 비밀은 아니지만 연산자 오버로딩 및 템플릿의 멋진 부작용입니다.

16
애완 동물 증오 : 모든 언어가 C 함수를 호출하도록 보장 할 수 있기 때문에 모든 언어가 사용하는 C 앱과 달리 C ++ 앱에 대해 정의 된 ABI가 없으며 아무도 C ++에 대해 동일한 작업을 수행 할 수 없습니다.
gbjbaanb

8
소멸자는 다형성으로 파괴하려는 경우에만 가상이어야합니다. 이는 첫 번째 지점과 약간 미묘하게 다릅니다.
David Rodríguez-dribeas

2
C ++ 0x에서는 로컬 유형을 템플릿 매개 변수로 사용할 수 있습니다.
tstenner

1
C ++ 0x를 사용하면 객체에 가상 함수 (예 : vtable)가 있으면 소멸자가 가상이됩니다.
Macke

NRVO를 잊지 마세요. 물론 프로그램 출력을 변경하지 않는 한 모든 최적화가 허용됩니다
jk.

26

정의되지 않은 동작없이 예상되는 의미를 사용하여 모든 클래스의 보호 된 데이터 및 함수 멤버에 액세스 할 수 있습니다. 방법을 보려면 계속 읽으십시오. 이에 대한 결함 보고서 도 읽어보십시오 .

일반적으로 C ++는 해당 클래스가 기본 클래스 인 경우에도 클래스 개체의 비 정적 보호 멤버에 액세스하는 것을 금지합니다.

struct A {
protected:
    int a;
};

struct B : A {
    // error: can't access protected member
    static int get(A &x) { return x.a; }
};

struct C : A { };

그것은 금지되어 있습니다. 당신과 컴파일러는 참조가 실제로 무엇을 가리키는 지 모릅니다. C클래스 B가 데이터에 대한 단서가없는 경우 객체 일 수 있습니다 . 이러한 액세스는가 x파생 클래스에 대한 참조이거나 파생 클래스에 대한 참조 인 경우에만 부여됩니다 . 그리고 다음과 같이 멤버를 읽는 "throw-away"클래스를 구성함으로써 임의의 코드 조각이 보호 된 멤버를 읽을 수 있습니다 std::stack.

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        static std::deque<int> &get(std::stack<int> &s) {
            // error: stack<int>::c is protected
            return s.c;
        }
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = pillager::get(s);
}

분명히, 이것은 너무 많은 피해를 입힐 것입니다. 하지만 이제 멤버 포인터를 사용하면 이러한 보호를 우회 할 수 있습니다! 요점은 멤버 포인터의 유형 이 주소를 가져올 때 지정한 클래스가 아니라 실제로 해당 멤버를 포함하는 클래스에 바인딩 된다는 것입니다. 이를 통해 확인을 우회 할 수 있습니다.

struct A {
protected:
    int a;
};

struct B : A {
    // valid: *can* access protected member
    static int get(A &x) { return x.*(&B::a); }
};

struct C : A { };

물론 std::stack예제 에서도 작동합니다 .

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        static std::deque<int> &get(std::stack<int> &s) {
            return s.*(pillager::c);
        }
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = pillager::get(s);
}

멤버 이름을 공개하고 기본 클래스의 멤버를 참조하는 파생 클래스에서 using 선언을 사용하면 훨씬 더 쉬워집니다.

void f(std::stack<int> &s) {
    // now, let's decide to mess with that stack!
    struct pillager : std::stack<int> {
        using std::stack<int>::c;
    };

    // haha, now let's inspect the stack's middle elements!
    std::deque<int> &d = s.*(&pillager::c);
}


26

또 다른 숨겨진 기능은 함수 포인터 또는 참조로 변환 할 수있는 클래스 객체를 호출 할 수 있다는 것입니다. 그 결과에 대해 과부하 해결이 수행되고 인수가 완벽하게 전달됩니다.

template<typename Func1, typename Func2>
class callable {
  Func1 *m_f1;
  Func2 *m_f2;

public:
  callable(Func1 *f1, Func2 *f2):m_f1(f1), m_f2(f2) { }
  operator Func1*() { return m_f1; }
  operator Func2*() { return m_f2; }
};

void foo(int i) { std::cout << "foo: " << i << std::endl; }
void bar(long il) { std::cout << "bar: " << il << std::endl; }

int main() {
  callable<void(int), void(long)> c(foo, bar);
  c(42); // calls foo
  c(42L); // calls bar
}

이를 "대리 호출 기능"이라고합니다.


1
그 결과에 대해 과부하 해결이 완료되었다고 말할 때 실제로 두 Functor로 변환 한 다음 과부하 해결을 의미합니까? 연산자 Func1 * () 및 연산자 Func2 * ()에서 인쇄를 시도했지만 호출 할 변환 연산자를 파악할 때 올바른 것을 선택하는 것 같습니다.
네비게이터

3
@navigator, 네, 개념적으로 둘 다로 변환 한 다음 가장 좋은 것을 선택합니다. 결과 유형에서 이미 무엇을 산출할지 알고 있기 때문에 실제로 호출 할 필요가 없습니다. 실제 호출은 최종적으로 선택된 것이 밝혀지면 수행됩니다.
Johannes Schaub-litb

26

숨겨진 기능 :

  1. 순수 가상 기능을 구현할 수 있습니다. 일반적인 예는 순수 가상 소멸자입니다.
  2. 함수가 예외 사양에 나열되지 않은 예외를 throw하지만 함수 std::bad_exception에 예외 사양이있는 경우 예외가로 변환되어 std::bad_exception자동으로 throw됩니다. 그렇게하면 최소한 a bad_exception가 던져 졌다는 것을 알 수 있습니다. 여기에서 자세한 내용을 읽어보십시오 .

  3. 기능 시도 블록

  4. 클래스 템플릿에서 typedef를 명확하게하는 템플릿 키워드. 멤버 템플릿 특수화의 이름은 후에 나타나는 경우 ., ->또는 ::운영자, 그 이름은 명시 적으로 규정 템플릿 매개 변수, 키워드 템플릿 접두사 멤버 템플릿 이름이 있습니다. 여기에서 자세한 내용을 읽어보십시오 .

  5. 함수 매개 변수 기본값은 런타임에 변경할 수 있습니다. 여기에서 자세한 내용을 읽어보십시오 .

  6. A[i] 잘 작동 i[A]

  7. 클래스의 임시 인스턴스를 수정할 수 있습니다! 상수가 아닌 멤버 함수는 임시 개체에서 호출 할 수 있습니다. 예를 들면 :

    struct Bar {
      void modify() {}
    }
    int main (void) {
      Bar().modify();   /* non-const function invoked on a temporary. */
    }

    여기에서 자세한 내용을 읽어보십시오 .

  8. :삼항 ( ?:) 연산자 표현식 의 앞뒤에 두 개의 다른 유형이있는 경우 표현식의 결과 유형은 둘 중 가장 일반적인 유형입니다. 예를 들면 :

    void foo (int) {}
    void foo (double) {}
    struct X {
      X (double d = 0.0) {}
    };
    void foo (X) {} 
    
    int main(void) {
      int i = 1;
      foo(i ? 0 : 0.0); // calls foo(double)
      X x;
      foo(i ? 0.0 : x);  // calls foo(X)
    }

P Daddy : A [i] == * (A + i) == * (i + A) == i [A]
abelenky

나는 정류를 얻었습니다. 이것은 [] 자체의 의미 론적 값이없고 단순히 "x [y]"가 "(* ((x) + (y ))) ". 내가 기대했던 것과는 전혀 다릅니다. 왜 이렇게 정의되었는지 궁금합니다.
P Daddy

C와의 역 호환성
jmucchiello

2
첫 번째 요점에 관하여 : 순수 가상 기능 구현 해야하는 한 가지 특별한 경우가 있습니다 : 순수 가상 소멸자.
Frerich Raabe

24

map::operator[]키가없는 경우 항목을 만들고 기본 구성 항목 값에 대한 참조를 반환합니다. 따라서 다음과 같이 작성할 수 있습니다.

map<int, string> m;
string& s = m[42]; // no need for map::find()
if (s.empty()) { // assuming we never store empty values in m
  s.assign(...);
}
cout << s;

나는 얼마나 많은 C ++ 프로그래머가 이것을 모른다는 것에 놀랐다.


11
반대쪽 끝에는 const 맵에서 operator []를 사용할 수 없습니다
David Rodríguez-dribeas

2
닉에게 +1, 사람들은에 대해 모르는 경우 미쳐 버릴 수 있습니다 .find().
LiraNuna

또는 " const map::operator[]오류 메시지를 생성합니다"
단지 누군가

2
언어의 기능이 아니라 표준 템플릿 라이브러리의 기능입니다. operator []가 유효한 참조를 반환하기 때문에 매우 분명합니다.
Ramon Zarazua B.

2
나는 이것이 기능이라는 것을 깨닫기 위해지도가 그런 방식으로 작동하지 않는 한동안 C #에서지도를 사용해야했다. 사용했던 것보다 더 짜증이 났다고 생각했는데 틀린 것 같습니다. C #에서 누락되었습니다.
sbi

20

이름없는 네임 스페이스에 함수 나 변수를 넣으면를 사용 static하여 파일 범위로 제한 할 수 없습니다.


"deprecates"는 강력한 용어입니다…
Potatoswatter

@Potato : 예전의 의견이지만, 표준은 네임 스페이스 범위에서 정적 사용이 더 이상 사용되지 않으며 명명되지 않은 네임 스페이스를 선호한다고 말합니다.
GManNickG 2010

@GMan : 문제가 없습니다. SO 페이지가 정말 "죽는"것 같지 않습니다. 이야기의 양쪽 모두를 위해 static글로벌 범위에서 어떤 식 으로든 사용되지 않습니다. (참고 : C ++ 03 §D.2)
Potatoswatter

아, 자세히 읽어 보면 "글로벌 네임 스페이스에 선언 된 이름에는 글로벌 네임 스페이스 범위 (글로벌 범위라고도 함)가 있습니다." 그게 정말 의미하는 건가요?
Potatoswatter 2010

@ 감자 : 응. :) staticuse는 클래스 유형 또는 함수 내에서만 사용해야합니다.
GManNickG 2010

19

클래스 템플릿에서 일반적인 친구 함수를 정의하려면 특별한주의가 필요합니다.

template <typename T> 
class Creator { 
    friend void appear() {  // a new function ::appear(), but it doesn't 
                           // exist until Creator is instantiated 
    } 
};
Creator<void> miracle;  // ::appear() is created at this point 
Creator<double> oops;   // ERROR: ::appear() is created a second time! 

이 예에서 두 개의 서로 다른 인스턴스화는 두 개의 동일한 정의를 만듭니다. 즉, ODR을 직접 위반합니다.

따라서 클래스 템플릿의 템플릿 매개 변수가 해당 템플릿에 정의 된 친구 함수의 유형에 나타나는지 확인해야합니다 (특정 파일에서 클래스 템플릿의 인스턴스화를 두 번 이상 방지하려는 경우가 아니라면). 이것을 이전 예제의 변형에 적용 해 보겠습니다.

template <typename T> 
class Creator { 
    friend void feed(Creator<T>*){  // every T generates a different 
                                   // function ::feed() 
    } 
}; 

Creator<void> one;     // generates ::feed(Creator<void>*) 
Creator<double> two;   // generates ::feed(Creator<double>*) 

면책 조항 : C ++ 템플릿 : 전체 가이드 / 섹션 8.4 에서이 섹션을 붙여 넣었습니다 .


18

void 함수는 void 값을 반환 할 수 있습니다.

거의 알려지지 않았지만 다음 코드는 괜찮습니다.

void f() { }
void g() { return f(); }

다음과 같이 이상하게 보이는 하나

void f() { return (void)"i'm discarded"; }

이에 대해 알면 일부 영역에서 이점을 얻을 수 있습니다. 한 가지 예 : void함수는 값을 반환 할 수 없지만 무효가 아닌 것으로 인스턴스화 될 수 있기 때문에 아무것도 반환하지 않을 수도 있습니다. 에 대한 오류가 발생하는 지역 변수에 값을 저장하는 대신 void값을 직접 반환합니다.

template<typename T>
struct sample {
  // assume f<T> may return void
  T dosomething() { return f<T>(); }

  // better than T t = f<T>(); /* ... */ return t; !
};

17

파일을 문자열 벡터로 읽어들입니다.

 vector<string> V;
 copy(istream_iterator<string>(cin), istream_iterator<string>(),
     back_inserter(V));

istream_iterator


8
또는 : vector <string> V ((istream_iterator <string> (cin)), istream_iterator <string>);
UncleBens

5
당신은 의미 vector<string> V((istream_iterator<string>(cin)), istream_iterator<string>());- 두 번째 PARAM 후 괄호가 누락
knittl

1
이것은 실제로 숨겨진 C ++ 기능이 아닙니다. 더 많은 STL 기능. STL! = 언어
Nick Bedford

14

비트 필드를 템플릿 할 수 있습니다.

template <size_t X, size_t Y>
struct bitfield
{
    char left  : X;
    char right : Y;
};

나는 아직 이것에 대한 어떤 목적도 생각해 내지 못했지만, 정말 놀랐습니다.


1
내가 최근에 n 비트 산술을 위해 제안한 여기를 참조하십시오. stackoverflow.com/questions/8309538/…
sehe

14

모든 프로그래밍 언어 중 가장 흥미로운 문법 중 하나입니다.

이 중 세 가지는 함께 속하고 두 가지는 완전히 다른 것입니다 ...

SomeType t = u;
SomeType t(u);
SomeType t();
SomeType t;
SomeType t(SomeType(u));

세 번째와 다섯 번째를 제외한 모든 SomeType객체는 스택에 객체를 정의 하고 초기화합니다 ( u처음 두 경우에는 기본 생성자가 있고 네 번째 경우에는 기본 생성자가 있습니다. 세 번째는 매개 변수를받지 않고를 반환하는 함수를 선언합니다 SomeType. 다섯 번째는 비슷하게 선언합니다. SomeType라는 유형의 값으로 하나의 매개 변수를 취하는 함수 u.


1st와 2nd 사이에 차이가 있습니까? 그래도 둘 다 초기화라는 것을 알고 있습니다.
Özgür

Comptrol : 그렇게 생각하지 않습니다. 둘 다 복사 생성자를 호출하게 될 것입니다. 첫 번째는 할당 연산자처럼 보이지만 실제로는 복사 생성자입니다.
abelenky

1
u가 SomeType과 다른 유형이면 첫 번째는 변환 생성자를 먼저 호출 한 다음 복사 생성자를 호출하는 반면 두 번째는 변환 생성자 만 호출합니다.
Eclipse

3
첫 번째는 생성자의 암시 적 호출이고 두 번째는 명시 적 호출입니다. 차이점을 확인하려면 다음 코드를 살펴보십시오. #include <iostream> class sss {public : explicit sss (int) {std :: cout << "int"<< std :: endl; }; sss (double) {std :: cout << "double"<< std :: endl; }; }; int main () {sss ddd (7); // int 생성자를 호출합니다. sss xxx = 7; // 이중 생성자를 호출합니다. return 0; }
Kirill V. Lyadvinsky 2009-06-23

True-생성자가 명시 적으로 선언되면 첫 번째 줄이 작동하지 않습니다.
Eclipse

12

전방 선언 제거 :

struct global
{
     void main()
     {
           a = 1;
           b();
     }
     int a;
     void b(){}
}
singleton;

? : 연산자를 사용하여 스위치 문 작성 :

string result = 
    a==0 ? "zero" :
    a==1 ? "one" :
    a==2 ? "two" :
    0;

한 줄로 모든 작업 수행 :

void a();
int b();
float c = (a(),b(),1.0f);

memset없이 구조체 제로화 :

FStruct s = {0};

각도 및 시간 값 정규화 / 래핑 :

int angle = (short)((+180+30)*65536/360) * 360/65536; //==-150

참조 할당 :

struct ref
{
   int& r;
   ref(int& r):r(r){}
};
int b;
ref a(b);
int c;
*(int**)&a = &c;

2
FStruct s = {};더 짧습니다.
Constantin

마지막 예제에서는 다음과 같이 더 간단합니다. a (); 비(); float c = 1.0f;
Zifre 2009

2
이 구문은 "float c = (a (), b (), 1.0f);" assigment-operation ( "c"의 지원)을 강조하는 데 유용합니다. 어시 그먼트 연산은 사용되지 않는 IMO가 될 가능성이 적기 때문에 프로그래밍에서 중요합니다. 이유를 모르겠다. 프로그램 상태가 모든 프레임에 다시 할당되는 함수형 프로그래밍과 관련이있을 수 있습니다. 추신. 그리고 아니오, "int d = (11,22,1.0f)"는 "1"과 같습니다. 1 분 전에 VS2008로 테스트했습니다.
AareP

2
+1 전화 해야하지 main않나요? global().main();싱글 톤에 대해 제안 하고 잊어 버릴 것입니다 ( 임시로 작업 할 수 있으며 수명이 연장됨 )
sehe

1
참조 할당이 이식 가능한지 의심 스럽습니다. 나는 앞으로 선언을 포기하는 구조체를 좋아합니다.
Thomas Eding 2012

12

삼항 조건부 연산자 ?:는 두 번째 및 세 번째 피연산자가 "동의 할 수있는"유형 (비공식적으로 말함)을 가져야합니다. 그러나이 요구 사항에는 한 가지 예외 (의도 된 말장난)가 있습니다. 두 번째 또는 세 번째 피연산자는 void다른 피연산자의 유형에 관계없이 throw 식 (type ) 이 될 수 있습니다 .

즉, ?:연산자를 사용하여 다음과 같이 유효하게 유효한 C ++ 표현식을 작성할 수 있습니다.

i = a > b ? a : throw something();

BTW, throw 표현식이 실제로 는 표현식 (유형의 void)이고 문이 아니라는 사실 은 C ++ 언어의 또 다른 잘 알려지지 않은 기능입니다. 이것은 무엇보다도 다음 코드가 완벽하게 유효 함을 의미합니다.

void foo()
{
  return throw something();
}

이런 식으로 수행하는 데 많은 의미가 없지만 (일부 일반적인 템플릿 코드에서는 유용 할 수 있습니다).


그만한 가치에 대해 Neil은 추가 정보를 위해 stackoverflow.com/questions/1212978/… 에 대한 질문이 있습니다.
GManNickG

12

지배 규칙은 유용하지만 거의 알려지지 않았습니다. 기본 클래스 격자를 통한 고유하지 않은 경로에 있더라도 멤버가 가상 기본 클래스에 속하면 부분적으로 숨겨진 멤버에 대한 이름 조회가 고유하다고 말합니다.

struct A { void f() { } };

struct B : virtual A { void f() { cout << "B!"; } };
struct C : virtual A { };

// name-lookup sees B::f and A::f, but B::f dominates over A::f !
struct D : B, C { void g() { f(); } };

나는 이것을 사용 하여 지배 규칙을 통해 가장 엄격한 정렬을 자동으로 파악하는 정렬 지원구현했습니다 .

이것은 가상 함수뿐만 아니라 typedef 이름, 정적 / 비가 상 멤버 및 기타 모든 것에 적용됩니다. 메타 프로그램에서 덮어 쓸 수있는 특성을 구현하는 데 사용되는 것을 보았습니다.


1
산뜻한. struct C당신의 예에 포함 된 특별한 이유는 ...? 건배.
Tony Delroy
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.