수색…


비고

과부하 해결은 여러 상황에서 발생합니다.

  • 명명 된 오버로드 된 함수를 호출합니다. 후보자는 이름 조회에서 찾은 모든 기능입니다.
  • 클래스 객체를 호출합니다. 후보자는 일반적으로 클래스의 모든 오버로드 된 함수 호출 연산자입니다.
  • 운영자의 사용. 후보는 네임 스페이스 범위에서 오버로드 된 연산자 함수, 왼쪽 클래스 개체에있는 오버로드 된 연산자 함수 (있는 경우) 및 기본 제공 연산자입니다.
  • 초기화를 위해 호출 할 올바른 변환 연산자 함수 또는 생성자를 찾기위한 오버로드 확인
    • 비표준 직접 초기화 ( Class c(value) )의 경우, 후보는 Class 생성자입니다.
    • 비 목록 복사 초기화 ( Class c = value ) 및 사용자 정의 변환 순서에서 호출 할 사용자 정의 변환 함수를 찾는 데 사용됩니다. 후보는 Class 의 생성자이며 소스가 클래스 객체 인 경우 해당 변환 연산자가 작동합니다.
    • 클래스 객체에서 비 클래스의 초기화 ( Nonclass c = classObject ). 후보는 이니셜 라이저 객체의 변환 연산자 함수입니다.
    • 클래스 오브젝트 (함께 참조 초기화 R &r = classObject 클래스에 직접 결합 될 수있는 값 수득 변환 연산자 기능을 갖는다), r . 후보는 그러한 변환 연산자 함수입니다.
    • 비 집합 클래스 객체 ( Class c{1, 2, 3} )의리스트 초기화의 경우, 후보는 과부하 해결을 통한 첫 번째 패스의 초기화리스트 생성자입니다. 이것이 실행 가능한 후보를 찾지 못하면, Class 의 생성자를 후보로 사용하여 두 번째 패스 오버로드 확인이 수행됩니다.

정확히 일치

매개 변수 유형에 필요한 변환이없는 과부하 또는 여전히 완전 일치로 간주되는 유형간에 필요한 변환 만 호출하기 위해 다른 변환이 필요한 과부하보다 선호됩니다.

void f(int x);
void f(double x);
f(42); // calls f(int)

인수가 같은 유형에 대한 참조에 바인드 될 때, 참조가 더 많은 Cv 규정이 있더라도 일치는 변환을 필요로하지 않는 것으로 간주됩니다.

void f(int& x);
void f(double x);
int x = 42;
f(x); // argument type is int; exact match with int&

void g(const int& x);
void g(int x);
g(x); // ambiguous; both overloads give exact match

과부하 해결을 위해 " T of array"유형은 " T to pointer"유형과 정확히 일치하는 것으로 간주되며 함수 유형 T 는 함수 포인터 유형 T* 와 정확히 일치하는 것으로 간주됩니다. 전환 수.

void f(int* p);
void f(void* p);

void g(int* p);
void g(int (&p)[100]);

int a[100];
f(a); // calls f(int*); exact match with array-to-pointer conversion
g(a); // ambiguous; both overloads give exact match

매개 변수 비용에 인수의 범주화

과부하 해결은 인수를 매개 변수로 전달하는 비용을 "시퀀스"라고하는 네 가지 범주 중 하나로 분류합니다. 각 시퀀스에는 0 개, 1 개 또는 여러 개의 전환이 포함될 수 있습니다.

  • 표준 변환 순서

    void f(int a); f(42);
    
  • 사용자 정의 변환 순서

    void f(std::string s); f("hello");
    
  • 줄임표 변환 시퀀스

    void f(...); f(42);
    
  • 목록 초기화 순서

    void f(std::vector<int> v); f({1, 2, 3});
    

일반 원칙은 표준 변환 시퀀스가 ​​가장 저렴하고 사용자 정의 변환 시퀀스 뒤에 줄임표 변환 시퀀스가 ​​오는 것입니다.

특별한 경우는 목록 초기화 시퀀스이며 변환을 구성하지는 않습니다 (이니셜 라이저 목록은 형식이있는 표현식이 아닙니다). 비용은 매개 변수 유형과 초기화 목록의 형식에 따라 다른 세 변환 시퀀스 중 하나와 동등하게 정의하여 결정됩니다.

이름 조회 및 액세스 확인

이름 조회 과부하 해결이 발생합니다. 즉, 이름 조회가 손실되면 오버로드 해결로 더 잘 일치하는 함수가 선택되지 않습니다.

void f(int x);
struct S {
    void f(double x);
    void g() { f(42); } // calls S::f because global f is not visible here,
                        // even though it would be a better match
};

과부하 해결은 액세스 검사 전에 발생합니다. 액세스 할 수없는 함수보다 액세스 가능성이 높은 함수 인 경우 과부하 해결로 액세스 할 수없는 함수를 선택할 수 있습니다.

class C {
  public:
    static void f(double x);
  private:
    static void f(int x);
};
C::f(42); // Error! Calls private C::f(int) even though public C::f(double) is viable.

마찬가지로 과부하 해결은 결과 호출이 explicit 올바른지 여부를 확인하지 않고 발생합니다.

struct X {
    explicit X(int );
    X(char );
};

void foo(X );
foo({4}); // X(int) is better much, but expression is 
          // ill-formed because selected constructor is explicit

전달 참조 오버로딩

전달 참조 오버로드가 너무 잘 일치 할 수 있으므로 전달 참조 오버로드를 제공 할 때는 매우주의해야합니다.

struct A {
    A() = default;           // #1
    A(A const& ) = default;  // #2

    template <class T>
    A(T&& );                 // #3
};

여기서 의도는 A 가 복사 가능하다는 것, 그리고 다른 멤버를 초기화 할 수있는 다른 생성자가 있다는 것입니다. 하나:

A a;     // calls #1
A b(a);  // calls #3!

건설 요청에는 두 가지 가능한 일치가 있습니다.

A(A const& ); // #2
A(A& );       // #3, with T = A&

모두 정확히 일치하지만 #3 보다 적은 이력서 restrict로 객체에 대한 참조를 취 #2 않는, 그래서 더 나은 표준 변환 순서를 가지고 최선의 가능한 기능입니다.

여기의 솔루션은 항상 이러한 생성자를 제한하는 것입니다 (예 : SFINAE 사용).

template <class T,
    class = std::enable_if_t<!std::is_convertible<std::decay_t<T>*, A*>::value>
    >
A(T&& );

여기에서 유형 특성은 A 에서 유래 된 A 또는 클래스를 A 에서 배제하여 모으는 것입니다. 이렇게하면 앞에서 설명한 예제에서이 생성자가 잘못 구성되어 과부하 집합에서 제거됩니다. 결과적으로 복사 생성자가 호출됩니다. 이는 우리가 원했던 것입니다.

과부하 해결 단계

과부하 해결 단계는 다음과 같습니다.

  1. 이름 조회를 통해 후보 기능을 찾습니다. 정규화되지 않은 호출은 일반 정규화되지 않은 조회뿐만 아니라 인수 종속적 인 조회 (적용 가능한 경우)를 모두 수행합니다.

  2. 후보 함수 집합을 실행 가능한 함수 집합으로 필터링합니다. 함수가 호출되는 인수와 함수가받는 매개 변수 사이에 암시 적 변환 시퀀스가있는 실행 가능 함수.

    void f(char);          // (1)
    void f(int ) = delete; // (2)
    void f();              // (3)
    void f(int& );         // (4)
    
    f(4); // 1,2 are viable (even though 2 is deleted!) 
          // 3 is not viable because the argument lists don't match
          // 4 is not viable because we cannot bind a temporary to 
          //     a non-const lvalue reference
    
  3. 가능한 가장 적합한 후보자를 고르십시오. 실행 가능한 함수 F1 다른 가능한 함수보다 더 함수 F2 의 각 인자에 대한 암시 적 변환 시퀀스 경우 F1 내의 대응하는 암시 적 변환 시퀀스보다 나쁘다 F2 및 ... :

    3.1. 일부 인수의 경우, F1 에서의 해당 인수에 대한 암시 적 변환 순서는 F2 에서의 인수에 대한 것보다 나은 변환 순서입니다.

    void f(int );  // (1)
    void f(char ); // (2)
    
    f(4);  // call (1), better conversion sequence
    

    3.2. 사용자 정의 변환에서 F1 반환에서 대상 유형으로의 표준 변환 순서는 반환 유형 F2 보다 더 나은 변환 순서입니다.

    struct A 
    {
        operator int();
        operator double();
    } a;
    
    int i = a; // a.operator int() is better than a.operator double() and a conversion
    float f = a; // ambiguous
    

    3.3. 직접 참조 바인딩에서 F1F2 에 의한 동일한 종류의 참조를 가지지 않습니다.

    struct A 
    {
        operator X&();  // #1
        operator X&&(); // #2
    };
    A a;
    X& lx = a;  // calls #1
    X&& rx = a; // calls #2
    

    3.4. F1 은 기능 템플릿 전문 분야가 아니지만 F2 는 또는

    template <class T> void f(T ); // #1
    void f(int );                  // #2
    
    f(42); // calls #2, the non-template
    

    3.5. F1F2 는 모두 기능 템플릿 전문이지만 F1F2 보다 전문화되어 있습니다.

    template <class T> void f(T );  // #1
    template <class T> void f(T* ); // #2
    
    int* p;
    f(p); // calls #2, more specialized
    

여기에 주문하는 것이 중요합니다. 더 나은 변환 순서 검사는 템플릿이 아닌 템플릿 검사와 비교되기 전에 수행됩니다. 이로 인해 포워딩 참조에서 오버로드가 발생하는 일반적인 오류가 발생합니다.

struct A {
    A(A const& ); // #1
    
    template <class T>
    A(T&& );      // #2, not constrained
};

A a;
A b(a); // calls #2!
        // #1 is not a template but #2 resolves to
        // A(A& ), which is a less cv-qualified reference than #1
        // which makes it a better implicit conversion sequence

마지막에 가장 적합한 실행 가능한 후보자가 하나도 없으면 통화가 모호합니다.

void f(double ) { }
void f(float ) { }

f(42); // error: ambiguous

산술 프로모션 및 전환

정수 유형을 해당 승격 된 유형으로 변환하는 것이 다른 정수 유형으로 변환하는 것보다 낫습니다.

void f(int x);
void f(short x);
signed char c = 42;
f(c); // calls f(int); promotion to int is better than conversion to short
short s = 42;
f(s); // calls f(short); exact match is better than promotion to int

floatdouble 승격시키는 것이 다른 부동 소수점 유형으로 변환하는 것보다 낫습니다.

void f(double x);
void f(long double x);
f(3.14f); // calls f(double); promotion to double is better than conversion to long double

판촉 이외의 산술 변환은 서로보다 좋지도 나쁘지도 않습니다.

void f(float x);
void f(long double x);
f(3.14); // ambiguous

void g(long x);
void g(long double x);
g(42); // ambiguous
g(3.14); // ambiguous

따라서 어떤 표준 유형의 정수 또는 부동 소수점 인수가있는 함수 f 를 호출 할 때 애매 모호하지 않게하려면 총 8 개의 과부하가 필요하므로 각 인수 유형에 대해 과부하 일치 정확하게 또는 승격 된 인수 유형을 사용하는 고유 한 오버로드가 선택됩니다.

void f(int x);
void f(unsigned int x);
void f(long x);
void f(unsigned long x);
void f(long long x);
void f(unsigned long long x);
void f(double x);
void f(long double x);

클래스 계층 구조에서 오버로딩

다음 예제는이 클래스 계층 구조를 사용합니다.

struct A { int m; };
struct B : A {};
struct C : B {};

파생 클래스 유형에서 기본 클래스 유형으로의 변환은 사용자 정의 변환보다 선호됩니다. 이것은 값 또는 참조로 전달할 때뿐만 아니라 파생 된 포인터를 포인터에서 포인터로 변환 할 때 적용됩니다.

struct Unrelated {
    Unrelated(B b);
};
void f(A a);
void f(Unrelated u);
B b;
f(b); // calls f(A)

파생 클래스에서 기본 클래스로의 포인터 변환도 void* 로의 변환보다 낫습니다.

void f(A* p);
void f(void* p);
B b;
f(&b); // calls f(A*)

동일한 상속 체인 내에 여러 오버로드가있는 경우 가장 많이 파생 된 기본 클래스 오버로드가 선호됩니다. 이는 가상 디스패치와 유사한 원칙에 기반합니다. "가장 전문화 된"구현이 선택됩니다. 그러나 과부하 해결은 항상 컴파일 타임에 발생하며 암시 적으로 다운 캐스팅되지 않습니다.

void f(const A& a);
void f(const B& b);
C c;
f(c); // calls f(const B&)
B b;
A& r = b;
f(r); // calls f(const A&); the f(const B&) overload is not viable

클래스에 반비례하는 멤버에 대한 포인터의 경우 반대 방향으로 비슷한 규칙이 적용됩니다. 파생 된 파생 클래스가 가장 적습니다.

void f(int B::*p);
void f(int C::*p);
int A::*p = &A::m;
f(p); // calls f(int B::*)

정수 및 변동성에 대한 과부하

가능한 경우 포인터 인수를 T* 매개 변수에 전달하는 것이 const T* 매개 변수에 전달하는 것보다 낫습니다.

struct Base {};
struct Derived : Base {};
void f(Base* pb);
void f(const Base* pb);
void f(const Derived* pd);
void f(bool b);

Base b;
f(&b); // f(Base*) is better than f(const Base*)
Derived d;
f(&d); // f(const Derived*) is better than f(Base*) though;
       // constness is only a "tie-breaker" rule

마찬가지로 T& 매개 변수에 인수를 전달하는 것은 가능한 경우 const T& 매개 변수에 전달하는 것보다 낫습니다. 둘 다 정확하게 일치하는 순위가 있어도 마찬가지입니다.

void f(int& r);
void f(const int& r);
int x;
f(x); // both overloads match exactly, but f(int&) is still better
const int y = 42;
f(y); // only f(const int&) is viable

이 규칙은 const-qualified 멤버 함수에도 적용됩니다. const-non-const 개체에 대한 변경 가능한 액세스와 const 개체에 대한 변경 불가능한 액세스를 허용하는 것이 중요합니다.

class IntVector {
  public:
    // ...
    int* data() { return m_data; }
    const int* data() const { return m_data; }
  private:
    // ...
    int* m_data;
};
IntVector v1;
int* data1 = v1.data();       // Vector::data() is better than Vector::data() const;
                              // data1 can be used to modify the vector's data
const IntVector v2;
const int* data2 = v2.data(); // only Vector::data() const is viable;
                              // data2 can't be used to modify the vector's data

같은 방식으로, 휘발성 과부하가 비 휘발성 과부하보다 바람직하지 않습니다.

class AtomicInt {
  public:
    // ...
    int load();
    int load() volatile;
  private:
    // ...
};
AtomicInt a1;
a1.load(); // non-volatile overload preferred; no side effect
volatile AtomicInt a2;
a2.load(); // only volatile overload is viable; side effect
static_cast<volatile AtomicInt&>(a1).load(); // force volatile semantics for a1


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