수색…


통사론

  • variable.member_var = 상수;
  • variable.member_function ();
  • variable_pointer-> member_var = 상수;
  • variable_pointer-> member_function ();

비고

사이의 유일한 차이점합니다 structclass 키워드는 기본, 멤버 변수, 멤버 함수, 그리고의 기본 클래스가 있다는 것입니다 struct 이다 public A의 동안, class 그들이 private . C ++ 프로그래머는 생성자와 소멸자가 있고 자체 불변성을 적용 할 수있는 경우 클래스라고 부르는 경향이 있습니다. 또는 단순한 값의 모음이라면 struct이지만 C ++ 언어 자체는 별다른 차이가 없습니다.

수업 기초

클래스 는 사용자 정의 유형입니다. 클래스는 class , struct 또는 union 키워드와 함께 도입됩니다. 구어체 사용에서 "클래스"라는 용어는 일반적으로 비노 색 클래스만을 나타냅니다.

클래스는 다음과 같은 클래스 멤버 의 컬렉션입니다.

  • 멤버 변수 ( "필드"라고도 함)
  • 멤버 함수 ( "메소드"라고도 함)
  • 멤버 유형 또는 typedef (예 : "중첩 클래스"),
  • 멤버 템플릿 (모든 종류의 : 변수, 함수, 클래스 또는 별칭 템플릿)

classstruct 멤버와 기지에 대한 기본 액세스 지정이로 선언 된 클래스에 대해 "개인"이라는 점을 제외 클래스 키라는 키워드는 주로 상호 교환 class 로 선언 된 클래스의 키와 "공개" struct 또는 union 키 (참조 : 접근 수정 자 ).

예를 들어, 다음 코드 스니 j은 동일합니다.

struct Vector
{
    int x;
    int y;
    int z;
};
// are equivalent to
class Vector
{
public:
    int x;
    int y;
    int z;
};

클래스`를 선언함으로써 새로운 타입이 프로그램에 추가되고, 그 클래스의 객체를 다음과 같이 인스턴스화 할 수 있습니다.

Vector my_vector;

클래스의 멤버는 도트 구문을 사용하여 액세스됩니다.

my_vector.x = 10;
my_vector.y = my_vector.x + 1; // my_vector.y = 11;
my_vector.z = my_vector.y - 4; // my:vector.z = 7;

액세스 지정자

액세스 지정자의 역할을하는 세 가지 키워드있습니다 . 이는 다른 지정자가 액세스 수준을 다시 변경할 때까지 지정자 다음의 클래스 멤버에 대한 액세스를 제한합니다.

예어 기술
public 누구나 액세스 할 수 있습니다.
protected 클래스 자체, 파생 클래스 및 친구들 만 액세스 할 수 있습니다.
private 클래스 자체 및 친구들 만 액세스 할 수 있습니다.

class 키워드를 사용하여 유형을 정의하면 기본 액세스 지정자는 private 이지만 struct 키워드를 사용하여 유형을 정의하면 기본 액세스 지정자는 public .

struct MyStruct { int x; };
class MyClass { int x; };

MyStruct s;
s.x = 9; // well formed, because x is public

MyClass c;
c.x = 9; // ill-formed, because x is private

액세스 지정자는 주로 내부 필드와 메소드에 대한 액세스를 제한하고 프로그래머가 변수를 직접 참조하는 대신 getter와 setter를 강제로 사용하도록 특정 인터페이스를 사용하도록합니다.

class MyClass {

public: /* Methods: */

    int x() const noexcept { return m_x; }
    void setX(int const x) noexcept { m_x = x; }

private: /* Fields: */

    int m_x;

};

protected 사용하면 유형의 특정 기능을 파생 클래스에만 액세스 할 수 있습니다. 예를 들어 다음 코드에서 메소드 calculateValue() 는 기본 클래스 Plus2Base 에서 파생 된 클래스 (예 : FortyTwo calculateValue() 에서만 액세스 할 수 있습니다.

struct Plus2Base {
    int value() noexcept { return calculateValue() + 2; }
protected: /* Methods: */
    virtual int calculateValue() noexcept = 0;
};
struct FortyTwo: Plus2Base {
protected: /* Methods: */
    int calculateValue() noexcept final override { return 40; }
};

friend 키워드는 보호 된 멤버와 비공개 멤버에 액세스하는 함수 또는 형식에 대한 액세스 예외를 추가하는 데 사용할 수 있습니다.

public , protectedprivate 키워드는 기본 클래스 하위 객체에 대한 액세스 권한을 부여하거나 제한하는 데 사용할 수도 있습니다. 상속 예제를 참조하십시오.

계승

클래스 / 구조체는 상속 관계를 가질 수 있습니다.

클래스 / 구조체 B 가 클래스 / 구조체 A 로부터 상속 받으면 B 는 부모 A 가짐을 의미합니다. 우리는 BA 의 파생 클래스 / 구조체이고 A 가 기본 클래스 / 구조체라고 말합니다.

struct A
{
public:
    int p1;
protected:
    int p2;
private:
    int p3;
};

//Make B inherit publicly (default) from A
struct B : A
{
};

클래스 / 구조체에는 3 가지 형태의 상속이 있습니다.

  • public
  • private
  • protected

기본 상속은 멤버의 기본 공개 설정과 동일합니다 ( public : struct 키워드 사용) public private ) 및 class 키워드 ( private .

struct 에서 파생 된 class 를 가질 수도 있습니다 (또는 그 반대). 이 경우, 기본 상속은 자식에 의해 제어되는, 그래서 struct A로부터 파생 class 공공 상속을 기본값으로하고, class A로부터 파생 struct 기본적으로 개인 상속을해야합니다.

public 상속 :

struct B : public A // or just `struct B : A`
{
    void foo()
    {
        p1 = 0; //well formed, p1 is public in B
        p2 = 0; //well formed, p2 is protected in B
        p3 = 0; //ill formed, p3 is private in A
    }
};

B b;
b.p1 = 1; //well formed, p1 is public
b.p2 = 1; //ill formed, p2 is protected
b.p3 = 1; //ill formed, p3 is inaccessible

private 상속 :

struct B : private A
{
    void foo()
    {
        p1 = 0; //well formed, p1 is private in B
        p2 = 0; //well formed, p2 is private in B
        p3 = 0; //ill formed, p3 is private in A
    }
};

B b;
b.p1 = 1; //ill formed, p1 is private
b.p2 = 1; //ill formed, p2 is private
b.p3 = 1; //ill formed, p3 is inaccessible

protected 상속 :

struct B : protected A
{
    void foo()
    {
        p1 = 0; //well formed, p1 is protected in B
        p2 = 0; //well formed, p2 is protected in B
        p3 = 0; //ill formed, p3 is private in A
    }
};

B b;
b.p1 = 1; //ill formed, p1 is protected
b.p2 = 1; //ill formed, p2 is protected
b.p3 = 1; //ill formed, p3 is inaccessible

protected 상속은 허용되지만 실제 사용은 거의 없습니다. 응용 프로그램에서 protected 상속이 사용되는 방법 중 하나는 부분 기본 클래스 특수화 (일반적으로 "제어 된 다형성"이라고 함)입니다.

OOP이 비교적 새로운 경우, (공개) 상속은 종종 "IS-A"관계를 모델링한다고합니다. 즉, 파생 클래스 인스턴스가 기본 클래스 인스턴스 이기도 한 경우에만 공용 상속이 올바른 것입니다.

이것은 나중에 Liskov Substitution Principle 으로 정제되었다. public 상속은 파생 된 클래스의 인스턴스가 가능한 모든 상황에서 기본 클래스의 인스턴스로 대체 될 수있는 경우에만 사용되어야한다.

사적 상속은 일반적으로 완전하게 다른 관계를 구현한다고 말합니다 ( "HAS-A"관계라고도 함). 예를 들어, Stack 클래스는 Vector 클래스에서 개별적으로 상속받을 수 있습니다. 개인 상속은 공용 상속보다 집계와 훨씬 더 유사합니다.

보호 된 상속은 거의 사용되지 않으며 어떤 유형의 관계가 구현되는지에 대한 일반적인 합의가 없습니다.

가상 상속

상속을 사용할 때 virtual 키워드를 지정할 수 있습니다.

struct A{};
struct B: public virtual A{};

클래스 B 가 가상베이스 A 가지면, 상속 트리의 대부분의 파생 클래스A 가 상주 할 것이므로 대부분의 파생 클래스가 해당 가상베이스를 초기화 할 책임이 있음을 의미합니다.

struct A
{
    int member;
    A(int param)
    {
        member = param;
    }
};

struct B: virtual A
{
    B(): A(5){}
};

struct C: B
{
    C(): /*A(88)*/ {}
};

void f()
{
    C object; //error since C is not initializing it's indirect virtual base `A`
}

우리가 /*A(88)*/ 주석 해제하면 C 는 이제 간접 가상베이스 A 초기화하기 때문에 어떤 에러도 발생하지 않을 것입니다.

또한 변수 object 만들 때 대부분의 파생 클래스는 C 이므로 CA 생성자를 생성하는 책임이 있으므로 A::member 값은 5 가 아니라 88 (우리가 B 유형의 오브젝트 생성).

다이아몬드 문제를 해결할 때 유용합니다.

  A                                        A   A
 / \                                       |   |
B   C                                      B   C
 \ /                                        \ /
  D                                          D
virtual inheritance                   normal inheritance

BC 모두 A 에서 상속 받고 DBC 에서 상속되므로 DA 인스턴스가 2 개 있습니다! 이는 컴파일러가 어느 클래스에서 그 멤버 ( B 상속하는 클래스 또는 C 상속 된 클래스)에 액세스 할지를 알 수있는 방법이 없기 때문에 A 에서 D 멤버에 액세스 할 때 모호함을 초래합니다. .

가상 상속은이 문제를 해결합니다. 가상베이스는 대부분의 파생 된 객체에만 있기 때문에 D 에서 A 인스턴스는 하나만 존재합니다.

struct A
{
    void foo() {}
};

struct B : public /*virtual*/ A {};
struct C : public /*virtual*/ A {};

struct D : public B, public C
{
    void bar()
    {
        foo(); //Error, which foo? B::foo() or C::foo()? - Ambiguous
    }
};

주석을 제거하면 모호성이 해결됩니다.

다중 상속

단일 상속을 제외하면 다음과 같습니다.

class A {};
class B : public A {};

다중 상속을 가질 수도 있습니다.

class A {};
class B {};
class C : public A, public B {};

C 는 이제 AB 를 동시에 상속받습니다.

참고 : 여러 개의 상속 된 classstruct 에서 같은 이름을 사용하면 모호함이 발생할 수 있습니다. 조심해!

다중 상속의 모호함

특정 상속에서는 다중 상속이 도움이 될 수 있지만, 다중 상속을 사용하는 동안 때로는 이상한 종류의 문제가 발생할 수 있습니다.

예 : 두 개의 기본 클래스는 파생 클래스에서 재정의되지 않은 동일한 이름의 함수를 가지며 파생 클래스의 객체를 사용하여 해당 함수에 액세스하는 코드를 작성하면 호출 할 함수를 결정할 수 없기 때문에 컴파일러에 오류가 표시됩니다. 다음은 다중 상속에서 이러한 유형의 모호성에 대한 코드입니다.

class base1
{
  public:
     void funtion( )
     { //code for base1 function }  
};
class base2
{
    void function( )
     { // code for base2 function } 
};

class derived : public base1, public base2
{
    
};

int main()
{
    derived obj;
    
  // Error because compiler can't figure out which function to call 
  //either function( ) of base1 or base2 .   
    obj.function( )  
}

그러나이 문제는 scope resolution 함수를 사용하여 base1 또는 base2 클래스를 지정할 함수를 지정하여 해결할 수 있습니다.

int main()
{
    obj.base1::function( );  // Function of class base1 is called. 
    obj.base2::function( );  // Function of class base2 is called.
}

반원들과 대화하기

클래스의 객체의 멤버 변수 및 멤버 함수에 액세스하려면 . 연산자가 사용됩니다.

struct SomeStruct {
  int a;
  int b;
  void foo() {}
};

SomeStruct var;
// Accessing member variable a in var.
std::cout << var.a << std::endl;
// Assigning member variable b in var.
var.b = 1;
// Calling a member function.
var.foo();

포인터를 통해 클래스의 멤버에 액세스 할 때 -> 연산자가 일반적으로 사용됩니다. 또는 인스턴스를 참조 해제 할 수 있습니다 . 이것은 일반적이지 않지만 연산자가 사용됩니다.

struct SomeStruct {
  int a;
  int b;
  void foo() {}
};

SomeStruct var;
SomeStruct *p = &var;
// Accessing member variable a in var via pointer.
std::cout << p->a << std::endl;
std::cout << (*p).a << std::endl;
// Assigning member variable b in var via pointer.
p->b = 1;
(*p).b = 1;
// Calling a member function via a pointer.
p->foo();
(*p).foo();

정적 클래스 멤버에 액세스 할 때 :: 연산자가 사용되지만 클래스의 인스턴스 대신 클래스 이름에 사용됩니다. 또는 정적 멤버는 인스턴스 또는 인스턴스를 가리키는 포인터에서를 사용하여 액세스 할 수 있습니다 . 또는 -> 연산자를 사용하여 비 정적 멤버에 액세스하는 구문과 동일한 구문을 사용합니다.

struct SomeStruct {
  int a;
  int b;
  void foo() {}

  static int c;
  static void bar() {}
};
int SomeStruct::c;

SomeStruct var;
SomeStruct* p = &var;
// Assigning static member variable c in struct SomeStruct.
SomeStruct::c = 5;
// Accessing static member variable c in struct SomeStruct, through var and p.
var.a = var.c;
var.b = p->c;
// Calling a static member function.
SomeStruct::bar();
var.bar();
p->bar();

배경

멤버 액세스 연산자 때문에 -> 연산자가 필요합니다 . 역 참조 연산자 * 보다 우선합니다.

*pap 참조 해제하여 (객체 p 가 가리키는 p 를 얻음) 멤버 a 액세스하는 것으로 기대할 수 있습니다. 그러나 실제로 p 의 멤버 a 에 액세스 한 다음 역 참조를 시도합니다. 즉 *pa*(pa) 와 같습니다. 첫째, 두 가지 사실들 때문에 위의 예에서,이 컴파일러 오류가 발생할 것 p 포인터이며, 회원이 없습니다 a . 둘째, a 는 정수이므로 역 참조 할 수 없습니다.

이 문제에 대해 일반적으로 사용되는 솔루션은 우선 순위를 명시 적으로 제어하는 ​​것입니다. (*p).a

대신, -> 연산자는 거의 항상 사용됩니다. 포인터를 처음 역 참조 한 다음 액세스하는 것은 짧은 명령입니다. 즉 (*p).ap->a 와 정확히 같습니다.

:: 연산자는 범위 연산자이며 네임 스페이스의 멤버에 액세스하는 것과 같은 방식으로 사용됩니다. 이는 정적 클래스 멤버가 해당 클래스의 범위에있는 것으로 간주되지만 해당 클래스의 인스턴스 멤버로 간주되지 않기 때문입니다. 정상적인 사용 .-> 또한 그들이 역사적인 이유로 인스턴스 멤버를,없는에도 불구하고, 정적 멤버 허용됩니다; 이 함수는 호출자가 주어진 멤버 함수가 정적인지 비 정적인지 여부를 고려할 필요가 없으므로 템플릿에 일반 코드를 작성하는 데 사용됩니다.

개인 상속 : 기본 클래스 인터페이스 제한

개인 상속은 클래스의 공용 인터페이스를 제한해야 할 때 유용합니다.

class A {
public:
    int move();
    int turn();
};

class B : private A {
public:
    using A::turn; 
};

B b;
b.move();  // compile error
b.turn();  // OK

이 접근법은 A 포인터 또는 참조로 캐스팅하여 A 공용 메소드에 대한 액세스를 효율적으로 방지합니다.

B b; 
A& a = static_cast<A&>(b); // compile error

공개 상속의 경우 이러한 캐스팅은 파생 된 B에서이를 방지하기위한 대체 방법에도 불구하고 숨기기와 같이 모든 A 공용 메소드에 대한 액세스를 제공합니다.

class B : public A {
private:
    int move();  
};

또는 개인용 :

class B : public A {
private:
    using A::move;  
};

두 경우 모두 가능합니다.

B b;
A& a = static_cast<A&>(b); // OK for public inheritance
a.move(); // OK

최종 클래스 및 구조체

C ++ 11

클래스를 파생시키는 것은 final 지정자로 금지 될 수 있습니다. 최종 수업을 선언합시다.

class A final {
};

이제 하위 클래스를 만들려고하면 컴파일 오류가 발생합니다.

// Compilation error: cannot derive from final class:
class B : public A {
};

최종 클래스는 클래스 계층 구조의 아무 곳에 나 나타날 수 있습니다.

class A {
};

// OK.
class B final : public A {
};

// Compilation error: cannot derive from final class B.
class C : public B {
};

우정

friend 키워드 는 클래스의 범위 외부에서 정의 된 경우에도 클래스의 개인 및 보호 된 멤버에 대한 액세스를 다른 클래스 및 함수에 부여하는 데 사용됩니다.

class Animal{
private:
    double weight;
    double height;
public:
    friend void printWeight(Animal animal);
    friend class AnimalPrinter;
    // A common use for a friend function is to overload the operator<< for streaming. 
    friend std::ostream& operator<<(std::ostream& os, Animal animal);
};

void printWeight(Animal animal)
{
    std::cout << animal.weight << "\n";
}

class AnimalPrinter
{
public:
    void print(const Animal& animal)
    {
        // Because of the `friend class AnimalPrinter;" declaration, we are
        // allowed to access private members here.
        std::cout << animal.weight << ", " << animal.height << std::endl;
    }
}

std::ostream& operator<<(std::ostream& os, Animal animal)
{
    os << "Animal height: " << animal.height << "\n";
    return os;
}

int main() {
    Animal animal = {10, 5};
    printWeight(animal);

    AnimalPrinter aPrinter;
    aPrinter.print(animal);

    std::cout << animal;
}

10
10, 5
Animal height: 5

중첩 클래스 / 구조체

classstruct 자체 내에 또 다른 class / struct 정의를 포함 할 수 있습니다.이 struct "중첩 클래스"라고합니다. 이 상황에서 포함하는 클래스는 "둘러싸는 클래스"라고합니다. 중첩 된 클래스 정의는 포함하는 클래스의 멤버로 간주되지만 그렇지 않으면 분리됩니다.

struct Outer {
    struct Inner { };
};

둘러싸는 클래스의 바깥 쪽에서 범위 연산자를 사용하여 중첩 된 클래스에 액세스합니다. 그러나 엔 클로징 클래스 내부에서 한정자없이 중첩 클래스를 사용할 수 있습니다.

struct Outer {
    struct Inner { };

    Inner in;
};

// ...

Outer o;
Outer::Inner i = o.in;

중첩되지 않은 class / struct 와 마찬가지로 멤버 함수와 정적 변수는 중첩 클래스 또는 포함하는 네임 스페이스에서 정의 할 수 있습니다. 그러나 중첩 클래스와 다른 클래스로 간주되므로 포함 클래스 내에서 정의 할 수 없습니다.

// Bad.
struct Outer {
    struct Inner {
        void do_something();
    };

    void Inner::do_something() {}
};


// Good.
struct Outer {
    struct Inner {
        void do_something();
    };

};

void Outer::Inner::do_something() {}

중첩되지 않은 클래스와 마찬가지로 중첩 클래스는 직접 사용되기 전에 정의 된 경우 나중에 전달되고 나중에 정의 될 수 있습니다.

class Outer {
    class Inner1;
    class Inner2;

    class Inner1 {};

    Inner1 in1;
    Inner2* in2p;

  public:
    Outer();
    ~Outer();
};

class Outer::Inner2 {};

Outer::Outer() : in1(Inner1()), in2p(new Inner2) {}
Outer::~Outer() {
    if (in2p) { delete in2p; }
}

C ++ 11

C ++ 11 이전에는 중첩 클래스는 그 클래스에서 오는 유형 이름, static 멤버 및 열거 자에만 액세스 할 수있었습니다. 동봉하는 클래스에 정의 된 다른 모든 멤버는 제한을 벗어났습니다.

C ++ 11

C ++ 11에서 중첩 클래스와 그 멤버는 마치 둘러싸는 클래스의 friend 것처럼 처리되고 일반적인 액세스 규칙에 따라 모든 멤버에 액세스 할 수 있습니다. 중첩 클래스의 멤버가 둘러싸는 클래스의 하나 이상의 비 정적 멤버를 평가해야하는 경우 인스턴스를 전달해야합니다.

class Outer {
    struct Inner {
        int get_sizeof_x() {
            return sizeof(x); // Legal (C++11): x is unevaluated, so no instance is required.
        }

        int get_x() {
            return x; // Illegal: Can't access non-static member without an instance.
        }

        int get_x(Outer& o) {
            return o.x; // Legal (C++11): As a member of Outer, Inner can access private members.
        }
    };

    int x;
};

반대로, 바깥 쪽 클래스는 명시 적으로 부여되는 허가없이 개인 회원에 액세스 할 수 없습니다, 따라서 중첩 된 클래스의 친구로 취급하고 있지 않습니다.

class Outer {
    class Inner {
        // friend class Outer;

        int x;
    };

    Inner in;

  public:
    int get_x() {
        return in.x; // Error: int Outer::Inner::x is private.
        // Uncomment "friend" line above to fix.
    }
};

중첩 된 클래스의 친구는 자동으로 둘러싸는 클래스의 친구로 간주되지 않습니다. 그들이 동봉하는 수업의 친구가되어야 할 필요가 있다면, 이것은 별도로 선언해야합니다. 반대로 둘러싸는 클래스는 자동적으로 중첩 된 클래스의 친구로 간주되지 않으므로 둘러싸는 클래스의 친구도 중첩 된 클래스의 친구로 간주되지 않습니다.

class Outer {
    friend void barge_out(Outer& out, Inner& in);

    class Inner {
        friend void barge_in(Outer& out, Inner& in);

        int i;
    };

    int o;
};

void barge_in(Outer& out, Outer::Inner& in) {
    int i = in.i;  // Good.
    int o = out.o; // Error: int Outer::o is private.
}

void barge_out(Outer& out, Outer::Inner& in) {
    int i = in.i;  // Error: int Outer::Inner::i is private.
    int o = out.o; // Good.
}

다른 모든 클래스 멤버와 마찬가지로 중첩 클래스는 공용 액세스 권한이있는 경우에만 클래스 외부에서 이름을 지정할 수 있습니다. 그러나 명시 적으로 이름을 지정하지 않는 한 액세스 한정자와 상관없이 액세스 할 수 있습니다.

class Outer {
    struct Inner {
        void func() { std::cout << "I have no private taboo.\n"; }
    };

  public:
    static Inner make_Inner() { return Inner(); }
};

// ...

Outer::Inner oi; // Error: Outer::Inner is private.

auto oi = Outer::make_Inner(); // Good.
oi.func();                     // Good.
Outer::make_Inner().func();    // Good.

중첩 클래스에 대한 유형 별칭을 만들 수도 있습니다. 형식 별칭이 둘러싼 클래스에 포함되어 있으면 중첩 형식과 형식 별칭이 다른 액세스 한정자를 가질 수 있습니다. 형식 별칭이 둘러싼 클래스 외부에 있으면 중첩 클래스 또는 그 typedef 공개해야합니다.

class Outer {
    class Inner_ {};

  public:
    typedef Inner_ Inner;
};

typedef Outer::Inner  ImOut; // Good.
typedef Outer::Inner_ ImBad; // Error.

// ...

Outer::Inner  oi; // Good.
Outer::Inner_ oi; // Error.
ImOut         oi; // Good.

다른 클래스와 마찬가지로 중첩 클래스는 다른 클래스에서 파생되거나 파생 될 수 있습니다.

struct Base {};

struct Outer {
    struct Inner : Base {};
};

struct Derived : Outer::Inner {};

이것은 프로그래머가 필요에 따라 중첩 된 클래스를 업데이트 할 수있게함으로써, 둘러싸는 클래스가 다른 클래스에 의해 파생되는 상황에서 유용 할 수 있습니다. 이것은 typedef와 결합되어 각각의 둘러싸는 클래스의 중첩 된 클래스에 대해 일관된 이름을 제공 할 수 있습니다 :

class BaseOuter {
    struct BaseInner_ {
        virtual void do_something() {}
        virtual void do_something_else();
    } b_in;

  public:
    typedef BaseInner_ Inner;

    virtual ~BaseOuter() = default;

    virtual Inner& getInner() { return b_in; }
};

void BaseOuter::BaseInner_::do_something_else() {}

// ---

class DerivedOuter : public BaseOuter {
    // Note the use of the qualified typedef; BaseOuter::BaseInner_ is private.
    struct DerivedInner_ : BaseOuter::Inner {
        void do_something() override {}
        void do_something_else() override;
    } d_in;

  public:
    typedef DerivedInner_ Inner;

    BaseOuter::Inner& getInner() override { return d_in; }
};

void DerivedOuter::DerivedInner_::do_something_else() {}

// ...

// Calls BaseOuter::BaseInner_::do_something();
BaseOuter* b = new BaseOuter;
BaseOuter::Inner& bin = b->getInner();
bin.do_something();
b->getInner().do_something();

// Calls DerivedOuter::DerivedInner_::do_something();
BaseOuter* d = new DerivedOuter;
BaseOuter::Inner& din = d->getInner();
din.do_something();
d->getInner().do_something();

상기의 경우, 두 BaseOuterDerivedOuter 회원이 입력 공급 Inner 로서 BaseInner_DerivedInner_ 각각. 이렇게하면 중첩 된 유형이 둘러싸는 클래스의 인터페이스를 손상시키지 않고 파생 될 수 있으며 중첩 된 유형을 다형성으로 사용할 수 있습니다.

회원 유형 및 별칭

classstruct 는 클래스 자체에 포함되어 있으며 클래스 자체의 구성원으로 처리되는 유형 별칭 인 멤버 유형 별칭을 정의 할 수도 있습니다.

struct IHaveATypedef {
    typedef int MyTypedef;
};

struct IHaveATemplateTypedef {
    template<typename T>
    using MyTemplateTypedef = std::vector<T>;
};

정적 멤버와 마찬가지로 이러한 typedef는 범위 연산자 인 :: 사용하여 액세스됩니다.

IHaveATypedef::MyTypedef i = 5; // i is an int.

IHaveATemplateTypedef::MyTemplateTypedef<int> v; // v is a std::vector<int>.

일반 유형 별명과 마찬가지로 각 구성원 유형 별명은 해당 정의 이전에 정의되었거나 별명이 지정된 모든 유형을 참조 할 수 있습니다. 마찬가지로 클래스 정의 외부에있는 typedef는 클래스 정의 뒤에 오는 경우 클래스 정의 내의 접근 가능한 typedef를 참조 할 수 있습니다.

template<typename T>
struct Helper {
    T get() const { return static_cast<T>(42); }
};

struct IHaveTypedefs {
//    typedef MyTypedef NonLinearTypedef; // Error if uncommented.
    typedef int MyTypedef;
    typedef Helper<MyTypedef> MyTypedefHelper;
};

IHaveTypedefs::MyTypedef        i; // x_i is an int.
IHaveTypedefs::MyTypedefHelper hi; // x_hi is a Helper<int>.

typedef IHaveTypedefs::MyTypedef TypedefBeFree;
TypedefBeFree ii;                  // ii is an int.

멤버 유형 별칭은 모든 액세스 수준으로 선언 할 수 있으며 적절한 액세스 한정자를 존중합니다.

class TypedefAccessLevels {
    typedef int PrvInt;

  protected:
    typedef int ProInt;

  public:
    typedef int PubInt;
};

TypedefAccessLevels::PrvInt prv_i; // Error: TypedefAccessLevels::PrvInt is private.
TypedefAccessLevels::ProInt pro_i; // Error: TypedefAccessLevels::ProInt is protected.
TypedefAccessLevels::PubInt pub_i; // Good.

class Derived : public TypedefAccessLevels {
    PrvInt prv_i; // Error: TypedefAccessLevels::PrvInt is private.
    ProInt pro_i; // Good.
    PubInt pub_i; // Good.
};

이것은 추상화 수준을 제공하는 데 사용할 수 있으므로 클래스 설계자가 의존하는 코드를 중단하지 않고 내부 설계를 변경할 수 있습니다.

class Something {
    friend class SomeComplexType;

    short s;
    // ...

  public:
    typedef SomeComplexType MyHelper;

    MyHelper get_helper() const { return MyHelper(8, s, 19.5, "shoe", false); }

    // ...
};

// ...

Something s;
Something::MyHelper hlp = s.get_helper();

이 상황에서 도우미 클래스가 SomeComplexType 에서 다른 유형으로 변경되면 typedeffriend 선언 만 수정해야합니다. 도우미 클래스가 동일한 기능을 제공하는 한, 이름으로 지정하는 대신 Something::MyHelper 로 사용하는 코드는 일반적으로 수정하지 않고도 작동합니다. 이러한 방식으로 기본 구현이 변경 될 때 수정해야하는 코드의 양을 최소화하므로 형식 이름은 한 위치에서만 변경하면됩니다.

이것은 원한다면 decltype 과 결합 될 수도 있습니다.

class SomethingElse {
    AnotherComplexType<bool, int, SomeThirdClass> helper;

  public:
    typedef decltype(helper) MyHelper;

  private:
    InternalVariable<MyHelper> ivh;

    // ...

  public:
    MyHelper& get_helper() const { return helper; }

    // ...
};

이 상황에서 SomethingElse::helper 의 구현을 변경하면 decltype 때문에 자동으로 typedef가 변경됩니다. 이렇게하면 helper 를 변경하려는 경우 필요한 수정 작업이 최소화되므로 사람의 실수 위험을 최소화 할 수 있습니다.

그러나 모든 것과 마찬가지로 이것은 너무 오래 걸릴 수 있습니다. typename이 내부적으로 한 번 또는 두 번만 사용되고 외부 적으로 0 번 사용되는 경우 예를 들어 별칭을 제공 할 필요가 없습니다. 프로젝트 전체에서 수백 또는 수천 번 사용되었거나 이름이 충분히 긴 경우 항상 절대적으로 사용하는 대신 typedef로 제공하는 것이 유용 할 수 있습니다. 생성 된 불필요한 잡음의 양과 앞으로의 호환성 및 편의성의 균형을 맞추어야합니다.


또한 템플릿 클래스와 함께 사용하여 클래스 외부의 템플릿 매개 변수에 대한 액세스를 제공 할 수 있습니다.

template<typename T>
class SomeClass {
    // ...

  public:
    typedef T MyParam;
    MyParam getParam() { return static_cast<T>(42); }
};

template<typename T>
typename T::MyParam some_func(T& t) {
    return t.getParam();
}

SomeClass<int> si;
int i = some_func(si);

일반적으로 컨테이너와 함께 사용되며 일반적으로 요소 유형 및 기타 도우미 유형을 구성원 유형 별칭으로 제공합니다. 예를 들어, C ++ 표준 라이브러리의 대부분의 컨테이너는 필요한 12 개의 다른 유형과 함께 다음과 같은 12 개의 헬퍼 유형을 제공합니다.

template<typename T>
class SomeContainer {
    // ...

  public:
    // Let's provide the same helper types as most standard containers.
    typedef T                                     value_type;
    typedef std::allocator<value_type>            allocator_type;
    typedef value_type&                           reference;
    typedef const value_type&                     const_reference;
    typedef value_type*                           pointer;
    typedef const value_type*                     const_pointer;
    typedef MyIterator<value_type>                iterator;
    typedef MyConstIterator<value_type>           const_iterator;
    typedef std::reverse_iterator<iterator>       reverse_iterator;
    typedef std::reverse_iterator<const_iterator> const_reverse_iterator;
    typedef size_t                                size_type;
    typedef ptrdiff_t                             difference_type;
};

C ++ 11 이전 버전에서는이 기능이 아직 제공되지 않았기 때문에 일반적으로 "템플릿 typedef "를 제공하는 데 사용되었습니다. 이것들은 별칭 템플릿의 도입과는 다소 공통점이 없지만 일부 상황에서는 여전히 유용합니다 (다른 상황에서는 별칭 템플릿과 결합됩니다. 이는 함수 포인터와 같은 복합 유형의 개별 구성 요소를 얻는데 매우 유용 할 수 있습니다 ). 일반적으로 형식 별칭에 대해 이름 type 을 사용합니다.

template<typename T>
struct TemplateTypedef {
    typedef T type;
}

TemplateTypedef<int>::type i; // i is an int.

이것은 하나 이상의 매개 변수를 정의하는 별칭을 제공하기 위해 여러 템플릿 매개 변수가있는 유형과 함께 사용되었습니다.

template<typename T, size_t SZ, size_t D>
class Array { /* ... */ };

template<typename T, size_t SZ>
struct OneDArray {
    typedef Array<T, SZ, 1> type;
};

template<typename T, size_t SZ>
struct TwoDArray {
    typedef Array<T, SZ, 2> type;
};

template<typename T>
struct MonoDisplayLine {
    typedef Array<T, 80, 1> type;
};

OneDArray<int, 3>::type     arr1i; // arr1i is an Array<int, 3, 1>.
TwoDArray<short, 5>::type   arr2s; // arr2s is an Array<short, 5, 2>.
MonoDisplayLine<char>::type arr3c; // arr3c is an Array<char, 80, 1>.

정적 클래스 멤버

클래스는 변수 또는 함수가 될 수있는 static 멤버도 가질 수 있습니다. 이것들은 클래스 범위에있는 것으로 간주되지만 정상적인 멤버로 취급되지 않습니다. 그들은 정적 저장 기간 (프로그램 시작부터 끝까지 존재 함)을 가지며 클래스의 특정 인스턴스에 묶이지 않으며 전체 클래스에 대해 하나의 복사본 만 존재합니다.

class Example {
    static int num_instances;      // Static data member (static member variable).
    int i;                         // Non-static member variable.

  public:
    static std::string static_str; // Static data member (static member variable).
    static int static_func();      // Static member function.

    // Non-static member functions can modify static member variables.
    Example() { ++num_instances; }
    void set_str(const std::string& str);
};

int         Example::num_instances;
std::string Example::static_str = "Hello.";

// ...

Example one, two, three;
// Each Example has its own "i", such that:
//  (&one.i != &two.i)
//  (&one.i != &three.i)
//  (&two.i != &three.i).
// All three Examples share "num_instances", such that:
//  (&one.num_instances == &two.num_instances)
//  (&one.num_instances == &three.num_instances)
//  (&two.num_instances == &three.num_instances)

정적 멤버 변수는 선언 된 클래스 내에서 정의 된 것으로 간주되지 않으므로 정의가 클래스 정의 외부에 있습니다. 프로그래머는 자신의 정의에서 정적 변수를 초기화 할 수는 있지만 필수는 아닙니다. 멤버 변수를 정의 할 때 static 이라는 키워드는 생략됩니다.

class Example {
    static int num_instances;               // Declaration.

  public:
    static std::string static_str;          // Declaration.

    // ...
};

int         Example::num_instances;         // Definition.  Zero-initialised.
std::string Example::static_str = "Hello."; // Definition.

이 때문에 정적 변수는 나중에 완전한 유형으로 정의되는 한 불완전한 유형 ( void 제외) 일 수 있습니다.

struct ForwardDeclared;

class ExIncomplete {
    static ForwardDeclared fd;
    static ExIncomplete    i_contain_myself;
    static int             an_array[];
};

struct ForwardDeclared {};

ForwardDeclared ExIncomplete::fd;
ExIncomplete    ExIncomplete::i_contain_myself;
int             ExIncomplete::an_array[5];

정적 멤버 함수는 일반 멤버 함수와 마찬가지로 클래스 정의 내부 또는 외부에서 정의 할 수 있습니다. 정적 멤버 변수와 마찬가지로, 키워드 static 클래스 정의 외부 고정 멤버 함수를 정의 할 때 생략된다.

// For Example above, either...
class Example {
    // ...

  public:
    static int static_func() { return num_instances; }

    // ...

    void set_str(const std::string& str) { static_str = str; }
};

// Or...

class Example { /* ... */ };

int  Example::static_func() { return num_instances; }
void Example::set_str(const std::string& str) { static_str = str; }

정적 멤버 변수가 선언되면 const 아니라 volatile 및 통합 또는 열거 타입이다, 그것은 클래스 정의 내부에서 선언에서 초기화 할 수 있습니다.

enum E { VAL = 5 };

struct ExConst {
    const static int ci = 5;              // Good.
    static const E ce = VAL;              // Good.
    const static double cd = 5;           // Error.
    static const volatile int cvi = 5;    // Error.

    const static double good_cd;
    static const volatile int good_cvi;
};

const double ExConst::good_cd = 5;        // Good.
const volatile int ExConst::good_cvi = 5; // Good.
C ++ 11

C ++ 11에서 LiteralType 유형의 정적 멤버 변수 ( constexpr 규칙에 따라 컴파일 타임에 생성 할 수있는 유형)는 constexpr 로 선언 할 수도 있습니다. 그렇다면 클래스 정의 내에서 초기화되어야합니다.

struct ExConstexpr {
    constexpr static int ci = 5;                      // Good.
    static constexpr double cd = 5;                   // Good.
    constexpr static int carr[] = { 1, 1, 2 };        // Good.
    static constexpr ConstexprConstructibleClass c{}; // Good.
    constexpr static int bad_ci;                      // Error.
};

constexpr int ExConstexpr::bad_ci = 5;                // Still an error.

const 또는 constexpr 정적 멤버 변수가 odr-used 인 경우 (비공식적으로 주소가 지정되었거나 참조에 할당 된 경우) 클래스 정의 외부에 별도의 정의가 있어야합니다. 이 정의에는 초기화 프로그램이 포함될 수 없습니다.

struct ExODR {
    static const int odr_used = 5;
};

// const int ExODR::odr_used;

const int* odr_user = & ExODR::odr_used; // Error; uncomment above line to resolve.

정적 멤버는 지정된 인스턴스에 연결되지 않으므로 범위 연산자 인 :: 사용하여 액세스 할 수 있습니다.

std::string str = Example::static_str;

그들은 마치 비 정적 인 멤버 인 것처럼 액세스 할 수도 있습니다. 이것은 역사적인 의미이지만, 멤버가 정적인지 비 정적인지에 대한 혼동을 방지하기 위해 범위 연산자보다 덜 일반적으로 사용됩니다.

Example ex;
std::string rts = ex.static_str;

클래스 멤버는 비 정적 클래스 멤버와 마찬가지로 범위를 한정하지 않고 정적 멤버에 액세스 할 수 있습니다.

class ExTwo {
    static int num_instances;
    int my_num;

  public:
    ExTwo() : my_num(num_instances++) {}

    static int get_total_instances() { return num_instances; }
    int get_instance_number() const { return my_num; }
};

int ExTwo::num_instances;

그것들은 mutable 될 수 없으며 필요가 없습니다. 특정 인스턴스에 묶여 있지 않기 때문에 인스턴스가 정적 멤버인지 정적 멤버인지는 정적 멤버에 영향을주지 않습니다.

struct ExDontNeedMutable {
    int immuta;
    mutable int muta;

    static int i;

    ExDontNeedMutable() : immuta(-5), muta(-5) {}
};
int ExDontNeedMutable::i;

// ...

const ExDontNeedMutable dnm;
dnm.immuta = 5; // Error: Can't modify read-only object.
dnm.muta = 5;   // Good.  Mutable fields of const objects can be written.
dnm.i = 5;      // Good.  Static members can be written regardless of an instance's const-ness.

정적 멤버는 정적 멤버가 아닌 액세스 수정자를 존중합니다.

class ExAccess {
    static int prv_int;

  protected:
    static int pro_int;

  public:
    static int pub_int;
};

int ExAccess::prv_int;
int ExAccess::pro_int;
int ExAccess::pub_int;

// ...

int x1 = ExAccess::prv_int; // Error: int ExAccess::prv_int is private.
int x2 = ExAccess::pro_int; // Error: int ExAccess::pro_int is protected.
int x3 = ExAccess::pub_int; // Good.

주어진 인스턴스에 묶여 있지 않기 때문에 정적 멤버 함수에는 this 포인터가 없습니다. 이로 인해 인스턴스를 전달하지 않으면 비 정적 멤버 변수에 액세스 할 수 없습니다.

class ExInstanceRequired {
    int i;

  public:
    ExInstanceRequired() : i(0) {}

    static void bad_mutate() { ++i *= 5; }                         // Error.
    static void good_mutate(ExInstanceRequired& e) { ++e.i *= 5; } // Good.
};

this 포인터를 가지지 않기 때문에, 그들의 주소는 포인터 - 투 - 멤버 함수에 저장 될 수없고 대신 함수에 대한 일반적인 포인터에 저장됩니다.

struct ExPointer {
           void nsfunc() {}
    static void  sfunc() {}
};

typedef void (ExPointer::* mem_f_ptr)();
typedef void (*f_ptr)();

mem_f_ptr p_sf = &ExPointer::sfunc; // Error.
    f_ptr p_sf = &ExPointer::sfunc; // Good.

this 포인터가 없기 때문에 const 또는 volatile 일 수 없으며 ref-qualifier도 가질 수 없습니다. 또한 가상 일 수도 없습니다.

struct ExCVQualifiersAndVirtual {
    static void   func()                {} // Good.
    static void  cfunc() const          {} // Error.
    static void  vfunc() volatile       {} // Error.
    static void cvfunc() const volatile {} // Error.
    static void  rfunc() &              {} // Error.
    static void rvfunc() &&             {} // Error.

    virtual static void vsfunc()        {} // Error.
    static virtual void svfunc()        {} // Error.
};

정적 인스턴스 변수는 주어진 인스턴스에 묶여 있지 않으므로 효과적으로 전역 변수로 취급됩니다. 프로그램이 시작될 때 만들어지며, 클래스가 실제로 존재하는지 여부에 관계없이 종료 될 때 파괴됩니다. 변수가 thread_local (C ++ 11 이상)으로 선언되지 않는 한 각 정적 멤버 변수의 복사본 하나만 존재합니다.이 경우 스레드 당 하나의 복사본이 있습니다.

정적 멤버 변수는 클래스가 외부 또는 내부 링크를 가지고 있는지 여부와 관계없이 클래스와 동일한 연결을가집니다. 로컬 클래스 및 명명되지 않은 클래스는 정적 멤버를 가질 수 없습니다.

비 정적 멤버 함수

클래스는 클래스의 개별 인스턴스에서 작동하는 비 정적 멤버 함수를 가질 수 있습니다.

class CL {
  public:
    void member_function() {}
};

이러한 함수는 다음과 같이 클래스의 인스턴스에서 호출됩니다.

CL instance;
instance.member_function();

클래스 정의 내부 또는 외부에서 정의 할 수 있습니다. 외부에 정의 된 경우 클래스 범위에 속하도록 지정됩니다.

struct ST {
    void  defined_inside() {}
    void defined_outside();
};
void ST::defined_outside() {}

CV 인증 및 / 또는 ref-qualified 일 수 있으며 호출 된 인스턴스를 보는 방법에 영향을 미칩니다. 이 함수는 인스턴스가 지정된 cv-qualifier를 갖는 것으로 간주합니다 (있는 경우). 호출되는 버전은 인스턴스의 cv 한정자를 기반으로합니다. 인스턴스와 동일한 cv-qualifier를 가진 버전이 없으면 사용 가능한 경우보다 많은 버전의 버전이 호출됩니다.

struct CVQualifiers {
    void func()                   {} // 1: Instance is non-cv-qualified.
    void func() const             {} // 2: Instance is const.

    void cv_only() const volatile {}
};

CVQualifiers       non_cv_instance;
const CVQualifiers      c_instance;

non_cv_instance.func(); // Calls #1.
c_instance.func();      // Calls #2.

non_cv_instance.cv_only(); // Calls const volatile version.
c_instance.cv_only();      // Calls const volatile version.
C ++ 11

멤버 함수 ref-qualifiers는 rvalue 인스턴스에서 함수를 호출 할 것인지 여부를 나타내고 함수 cv-qualifiers와 동일한 구문을 사용합니다.

struct RefQualifiers {
    void func() &  {} // 1: Called on normal instances.
    void func() && {} // 2: Called on rvalue (temporary) instances.
};

RefQualifiers rf;
rf.func();              // Calls #1.
RefQualifiers{}.func(); // Calls #2.

필요한 경우 CV 한정어와 ref 한정자가 결합 될 수 있습니다.

struct BothCVAndRef {
    void func() const& {} // Called on normal instances.  Sees instance as const.
    void func() &&     {} // Called on temporary instances.
};

그들은 가상 일 수도 있습니다. 이것은 다형성의 기본이며, 하위 클래스가 자신의 기능을 제공하면서 상위 클래스와 동일한 인터페이스를 제공 할 수 있습니다.

struct Base {
    virtual void func() {}
};
struct Derived {
    virtual void func() {}
};

Base* bp = new Base;
Base* dp = new Derived;
bp.func(); // Calls Base::func().
dp.func(); // Calls Derived::func().

자세한 내용은 여기를 참조 하십시오 .

명명되지 않은 구조체 / 클래스

struct 가 허용됩니다 (유형은 이름 없음).

void foo()
{
    struct /* No name */ {
        float x;
        float y;
    } point;
    
    point.x = 42;
}

또는

struct Circle
{
    struct /* No name */ {
        float x;
        float y;
    } center; // but a member name
    float radius;
};

나중에

Circle circle;
circle.center.x = 42.f;

그러나 익명 struct (이름이없는 유형 및 이름이없는 객체)

struct InvalidCircle
{
    struct /* No name */ {
        float centerX;
        float centerY;
    }; // No member either.
    float radius;
};

참고 : 일부 컴파일러에서는 익명 struct확장자 로 사용할 수 있습니다 .

C ++ 11
  • lamdba 는 특별한 이름없는 struct 로 볼 수 있습니다.

  • decltype이름없는 struct 유형을 검색 할 수 있습니다.

    decltype(circle.point) otherPoint;
    
  • 이름없는 struct 인스턴스는 template 메소드의 매개 변수가 될 수 있습니다.

    void print_square_coordinates()
    {
        const struct {float x; float y;} points[] = {
            {-1, -1}, {-1, 1}, {1, -1}, {1, 1}
        };
    
        // for range relies on `template <class T, std::size_t N> std::begin(T (&)[N])`
        for (const auto& point : points) { 
            std::cout << "{" << point.x << ", " << point.y << "}\n";
        }
    
        decltype(points[0]) topRightCorner{1, 1};
        auto it = std::find(points, points + 4, topRightCorner);
        std::cout << "top right corner is the "
                  << 1 + std::distance(points, it) << "th\n";
    }
    


Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow