가상 함수가 기본 매개 변수를 가질 수 있습니까?


164

기본 클래스 (또는 인터페이스 클래스)를 선언하고 하나 이상의 매개 변수에 대한 기본값을 지정하면 파생 클래스가 동일한 기본값을 지정해야하며 그렇지 않은 경우 파생 클래스에서 어떤 기본값이 나타 납니까?

부록 : 또한이 시나리오에서 다른 컴파일러와 "권장"실습에 대한 입력을 처리하는 방법에 관심이 있습니다.


1
이것은 테스트하기 쉬운 것 같습니다. 사용해 보셨습니까?
그리고

22
나는 그것을 시도하는 과정에 있지만 행동이 어떻게 "정의"되었는지에 대한 구체적인 정보를 찾지 못했기 때문에 결국 내 특정 컴파일러에 대한 답을 찾을 것이지만 모든 컴파일러가 똑같이 할 것인지는 알려주지 않을 것입니다 알맞은 것. 나는 또한 권장 연습에 관심이 있습니다.
Arnold Spence

1
동작이 잘 정의되어 있으며, 잘못된 컴파일러를 찾게 될 것입니다 (gcc 1.x 또는 VC ++ 1.0 또는 이와 유사한 것을 테스트하는 경우). 권장되는 방법은이 작업을 수행하지 않는 것입니다.
Jerry Coffin

답변:


213

가상에는 기본값이있을 수 있습니다. 기본 클래스의 기본값은 파생 클래스에 의해 상속되지 않습니다.

기본 클래스 또는 파생 클래스와 같이 사용되는 기본값은 함수를 호출하는 데 사용되는 정적 유형에 의해 결정됩니다. 기본 클래스 객체, 포인터 또는 참조를 통해 호출하면 기본 클래스에 표시된 기본값이 사용됩니다. 반대로 파생 클래스 개체, 포인터 또는 참조를 통해 호출하면 파생 클래스에 표시된 기본값이 사용됩니다. 이를 나타내는 표준 인용 아래에 예가 있습니다.

일부 컴파일러는 다른 작업을 수행 할 수 있지만 C ++ 03 및 C ++ 11 표준은 다음과 같이 말합니다.

8.3.6.10 :

가상 함수 호출 (10.3)은 객체를 나타내는 포인터 또는 참조의 정적 유형에 의해 결정된 가상 함수 선언에서 기본 인수를 사용합니다. 파생 클래스의 재정의 함수는 재정의하는 함수에서 기본 인수를 얻지 않습니다. 예:

struct A {
  virtual void f(int a = 7);
};
struct B : public A {
  void f(int a);
};
void m()
{
  B* pb = new B;
  A* pa = pb;
  pa->f(); //OK, calls pa->B::f(7)
  pb->f(); //error: wrong number of arguments for B::f()
}

다음은 기본 설정을 보여주는 샘플 프로그램입니다. 나는 간결성을 위해 es struct대신에 s를 사용 하고 있으며 기본 가시성을 제외하고 거의 모든면에서 정확히 동일합니다.classclassstruct

#include <string>
#include <sstream>
#include <iostream>
#include <iomanip>

using std::stringstream;
using std::string;
using std::cout;
using std::endl;

struct Base { virtual string Speak(int n = 42); };
struct Der : public Base { string Speak(int n = 84); };

string Base::Speak(int n) 
{ 
    stringstream ss;
    ss << "Base " << n;
    return ss.str();
}

string Der::Speak(int n)
{
    stringstream ss;
    ss << "Der " << n;
    return ss.str();
}

int main()
{
    Base b1;
    Der d1;

    Base *pb1 = &b1, *pb2 = &d1;
    Der *pd1 = &d1;
    cout << pb1->Speak() << "\n"    // Base 42
        << pb2->Speak() << "\n"     // Der 42
        << pd1->Speak() << "\n"     // Der 84
        << endl;
}

이 프로그램의 출력 (MSVC10 및 GCC 4.4)은 다음과 같습니다.

Base 42
Der 42
Der 84

참조 주셔서 감사합니다. 컴파일러에서 합리적으로 기대할 수있는 동작을 알려줍니다.
Arnold Spence

이것은 이전 요약에 대한 수정입니다 : 나는이 답변을 참조로 받아 들일 것이며 집단 권장 사항은 조상에 이전에 지정된 기본 매개 변수를 변경하지 않는 한 가상 함수에서 기본 매개 변수를 갖는 것이 좋다는 것을 언급 할 것입니다 수업.
Arnold Spence

gcc 4.8.1을 사용하고 있으며 "잘못된 인수 수"라는 컴파일 오류가 발생하지 않습니다 !!! 버그를 찾기 위해 하루 반을
걸렸습니다

2
그러나 그 이유가 있습니까? 정적 유형에 의해 결정되는 이유는 무엇입니까?
user1289

2
원치 않는 뭔가되는 문제에 대한 경고와 같은 가상 방법에 대한 연타 - 깔끔한 치료의 기본 매개 변수 github.com/llvm-mirror/clang-tools-extra/blob/master/clang-tidy/...
마틴 Pecka

38

이것은 Herb Sutter의 금주 의 초기 전문가 중 하나의 주제였습니다. 게시물 .

그가 그 주제에 대해 가장 먼저 말한 것은하지 말아야한다는 것입니다.

보다 상세하게 예, 다른 기본 매개 변수를 지정할 수 있습니다. 그들은 가상 기능과 같은 방식으로 작동하지 않습니다. 가상 함수는 객체의 동적 유형에서 호출되는 반면 기본 매개 변수 값은 정적 유형을 기반으로합니다.

주어진

class A {
    virtual void foo(int i = 1) { cout << "A::foo" << i << endl; }
};
class B: public A {
    virtual void foo(int i = 2) { cout << "B::foo" << i << endl; }
};
void test() {
A a;
B b;
A* ap = &b;
a.foo();
b.foo();
ap->foo();
}

A :: foo1 B :: foo2 B :: foo1을 가져와야합니다.


7
감사. Herb Sutter의 "하지 마십시오"는 약간의 무게를 지닙니다.
Arnold Spence

2
@ArnoldSpence, 실제로 Herb Sutter는이 권장 사항을 뛰어 넘습니다. : 그는 인터페이스 모두에서 가상 메서드가 포함되지한다고 생각 gotw.ca/publications/mill18.htm을 . 일단 메소드가 구체적이고 재정의 될 수없는 경우 (기본적으로) 매개 변수를 지정하는 것이 안전합니다.
Mark Ransom

1
나는 그가 "하지 않는다 의미 믿고 그렇게 하지"가상 메소드의 기본 매개 변수를 지정하지 않은 "방법을 재정의"기본 매개 변수의 기본값을 변경하지 않는다 "였다"
Weipeng L을

6

기본 인수 는 객체 의 정적 유형에 따라 결정 되지만 virtual전달 된 함수는 동적에 따라 달라 지기 때문에 이것은 나쁜 생각 입니다. 유형 입니다.

즉, 기본 인수로 함수를 호출하면 함수의 유무에 관계없이 컴파일시 기본 인수가 대체됩니다 virtual.

@cppcoder는 그의 [닫힌] 질문 에서 다음 예제를 제공했습니다 .

struct A {
    virtual void display(int i = 5) { std::cout << "Base::" << i << "\n"; }
};
struct B : public A {
    virtual void display(int i = 9) override { std::cout << "Derived::" << i << "\n"; }
};

int main()
{
    A * a = new B();
    a->display();

    A* aa = new A();
    aa->display();

    B* bb = new B();
    bb->display();
}

다음과 같은 출력이 생성됩니다.

Derived::5
Base::5
Derived::9

위의 설명을 통해 이유를 쉽게 알 수 있습니다. 컴파일시 컴파일러는 정적 유형의 포인터 멤버 함수에서 기본 인수를 대체하여 함수를 main다음과 같습니다.

    A * a = new B();
    a->display(5);

    A* aa = new A();
    aa->display(5);

    B* bb = new B();
    bb->display(9);

4

다른 답변에서 볼 수 있듯이 이것은 복잡한 주제입니다. 이것을 시도하거나 그것이 무엇을하는지 이해하는 대신 (지금 요청해야하는 경우, 관리자는 1 년 후에 요청하거나 조회해야합니다).

대신 기본 클래스에서 기본 매개 변수를 사용하여 공개 비가 상 함수를 만듭니다. 그런 다음 기본 매개 변수가없는 개인용 또는 보호 된 가상 기능을 호출하고 필요에 따라 하위 클래스에서 대체됩니다. 그런 다음 어떻게 작동하는지에 대해 걱정할 필요가 없으며 코드가 매우 분명합니다.


1
전혀 복잡하지 않습니다. 기본 매개 변수는 이름 확인과 함께 검색됩니다. 그들은 같은 규칙을 따릅니다.
Edward Strange

4

이것은 아마도 테스트를 통해 합리적으로 잘 이해할 수있는 것입니다 (즉, 대부분의 컴파일러가 거의 확실하게 올바른 언어를 얻는 주류 부분입니다. 컴파일러 간의 차이점을 보지 않으면 출력은 권위있는 것으로 간주 될 수 있습니다).

#include <iostream>

struct base { 
    virtual void x(int a=0) { std::cout << a; }
    virtual ~base() {}
};

struct derived1 : base { 
    void x(int a) { std:: cout << a; }
};

struct derived2 : base { 
    void x(int a = 1) { std::cout << a; }
};

int main() { 
    base *b[3];
    b[0] = new base;
    b[1] = new derived1;
    b[2] = new derived2;

    for (int i=0; i<3; i++) {
        b[i]->x();
        delete b[i];
    }

    derived1 d;
    // d.x();       // won't compile.
    derived2 d2;
    d2.x();
    return 0;
}

4
@GMan : [조심스럽게 결백 한] 누출? :-)
Jerry Coffin

가상 소멸자가 부족하다고 말하고있는 것 같습니다. 그러나이 경우 누출되지 않습니다.
John Dibling

1
@Jerry, 기본 클래스 포인터를 통해 파생 된 객체를 삭제하는 경우 소멸자는 가상입니다. 그렇지 않으면 기본 클래스 소멸자가 모두 호출됩니다. 여기에는 소멸자가 없으므로 괜찮습니다. :-)
chappar

2
@ 존 : 원래 삭제가 없었습니다. 나는 가상 소멸자의 부족을 완전히 무시했다. 그리고 ... @chappar : 아뇨, 괜찮습니다. 그것은 해야한다 가상 소멸자는 기본 클래스를 삭제해야하거나 정의되지 않은 동작을 얻을. (이 코드는 정의되지 않은 동작을 가지고 있습니다.) 파생 클래스가 갖는 데이터 또는 소멸자와는 아무 관련이 없습니다.
GManNickG

@Chappar : 코드는 원래 아무것도 삭제하지 않았습니다. 그것은 현재 당면한 질문과 관련이 없지만, 기본 클래스에 가상 dtor를 추가했습니다. 사소한 dtor로 거의 문제가되지 않지만 GMan은 코드가 없으면 UB가 있다는 것이 전적으로 정확합니다.
Jerry Coffin

4

다른 답변에서 자세히 설명했듯이 나쁜 생각입니다. 그러나 간단하고 효과적인 해결책에 대해서는 아무도 언급하지 않았으므로 다음과 같습니다. 매개 변수를 struct로 변환 한 다음 struct 멤버에 대한 기본값을 가질 수 있습니다!

대신에

//bad idea
virtual method1(int x = 0, int y = 0, int z = 0)

이 작업을 수행,

//good idea
struct Param1 {
  int x = 0, y = 0, z = 0;
};
virtual method1(const Param1& p)
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.