2009-11-21 2 views
30

dll/공유 객체에 배포 될 C++ 라이브러리 용 API를 설계하고 있습니다. 라이브러리에는 가상 함수가 포함 된 다형성 클래스가 들어 있습니다. 이러한 가상 함수를 DLL API에 노출하면 이전 버전의 라이브러리 용으로 빌드 된 응용 프로그램과의 이진 호환성을 손상시키지 않으면 서 동일한 클래스를 더 많은 가상 함수로 확장 할 수 있다는 우려에서 벗어났습니다.이진 호환 확장 성을위한 C++ API를 설계하는 방법

가상 함수가있는 모든 클래스를 숨기려면 PImpl이라는 관용구를 사용하는 것이 하나의 방법이지만 응용 프로그램에는 라이브러리의 클래스를 서브 클래 싱하고 가상 메서드를 재정의 할 가능성이 없어집니다.

역방향 바이너리 호환성을 유지하면서 새 버전의 dll에서 가상 클래스가 아닌 API를 확장 할 가능성을 잃지 않으면 서 응용 프로그램에서 서브 클래 싱 할 수있는 API 클래스를 어떻게 설계 하시겠습니까?

업데이트 : 라이브러리의 대상 플랫폼은 windows/msvc 및 linux/gcc입니다.

답변

29

몇 달 전에 "GNU/Linux 시스템에서 C++로 구현 된 공유 라이브러리의 바이너리 호환성"이라는 기사를 작성했습니다 [pdf]. 개념은 Windows 시스템과 비슷하지만 정확히 동일하지는 않습니다. 그러나이 기사를 읽으면 호환성과 관련이있는 C++ 바이너리 수준에서 어떤 일이 벌어지고 있는지에 대한 개념을 알 수 있습니다.

그런데 GCC 응용 프로그램 바이너리 인터페이스는 표준 문서 초안 "Itanium ABI"에 요약되어 있으므로 선택하는 코딩 표준에 대한 공식적인 근거가 있습니다.

예를 들어 GCC에서 다른 클래스가 상속하지 않으면 더 많은 가상 함수로 클래스를 확장 할 수 있습니다. 더 나은 규칙 집합에 대한 기사를 읽어보십시오.

하지만 어쨌든 규칙은 때때로 이해하기에는 너무 복잡합니다. 따라서 두 가지 버전의 호환성을 확인하는 도구에 관심이있을 수 있습니다. abi-compliance-checker for Linux.

+0

게시 한 PDF 파일의 호스트가 완료된 것으로 보입니다. 다시 보내 주시겠습니까? –

+0

@ MichałGórny 그것은 다시 돌아오고있는 것처럼 보이지만, 나는 그것을 (여기에서) (http://static.coldattic.info/restricted/science/syrcose09/cppbincomp.pdf) 재 호스팅했습니다. –

1

C++ 이진 호환성은 일반적으로 상속이 없어도 어렵습니다. 예를 들어 GCC를보십시오. 지난 10 년 동안, 나는 그들이 얼마나 많은 ABI 변화를 겪었는지 확신 할 수 없다. 그런 다음 MSVC에는 다른 규칙이 있으므로 GCC와 연결하거나 그 반대의 경우도 수행 할 수 없습니다.이 문제를 C 환경과 비교하면 컴파일러 상호 작용이 조금 더 나은 것으로 보입니다.

Windows 사용자는 COM을 확인해야합니다. 새로운 기능을 소개하면서 인터페이스를 추가 할 수 있습니다. 그런 다음 호출자는 새 기능을 노출하기 위해 새 번호를 QueryInterface()으로 가져올 수 있으며 많은 변경 작업을 마친 경우에도 이전 구현을 그대로 두거나 이전 인터페이스에 대한 shim을 작성할 수 있습니다.

+4

"지난 10 년 동안 ABI가 얼마나 많은 변화를 겪었는지 확신 할 수 없습니다." 얼마나 많은지 알려주지. ** 하나 **. 현재 ABI는 공식화되어 표준 문서에 설명되어 있습니다. –

+1

2.95와 3.0 (BeOS와 Haiku에 심각한 문제가 있음) 사이에 중대한 단절이 있었지만, 3.2와 3.3 또는 그 사이의 또 다른 중대한 단절을 상기하게됩니다. (이것은 Gentoo에서 약간의 문제를 일으켰습니다) . 이것은 틀린가? – greyfade

+1

오, 나는 3.0 세가 10 년 이상이라고 생각했다. 그래, 둘. 하나는 2001 년 6 월에 출시되었으며 3.0이 출시되었습니다. 그 후로 그들은 좋은 오래 살 수있는 ABI 디자인을 생산하기 위해 일했고 2002 년 8 월에 3.2 버전을 채택했습니다. 7 년 전의 마지막이었습니다. –

11

라이브러리를 작성할 때 바이너리 호환성을 목표로 할 때해야 할 것과하지 말아야 할을 설명하는 KDE의 지식 기반에 흥미로운 기사가있다 : Policies/Binary Compatibility Issues With C++

1

난 당신이 하위 클래스의 문제를 오해 생각은.

// .h 
class Derived 
{ 
public: 
    virtual void test1(); 
    virtual void test2(); 
private; 
    Impl* m_impl; 
}; 

// .cpp 
struct Impl: public Base 
{ 
    virtual void test1(); // override Base::test1() 
    virtual void test2(); // override Base::test2() 

    // data members 
}; 

void Derived::test1() { m_impl->test1(); } 
void Derived::test2() { m_impl->test2(); } 

참조 : 여기

은 Pimpl입니까?Base의 가상 메서드를 재정의해도 문제가되지 않으므로 virtualDerived에 다시 선언하면 Derived에서 파생 된 파생 클래스가 파생물을 다시 작성할 수 있음을 알 수 있습니다 (원하는 경우에만 해당 방법을 사용하면 좋은 방법입니다). 자들이 부족한 사람들을 위해 final을 제공합니다.) 에서 자신을 위해 다시 정의 할 수 있습니다.이 경우 Base 버전으로 전화 할 수도 있습니다.

Pimpl에는 문제가 없습니다.

한편, 당신은 다형성을 잃어 버리는 경우가 있습니다. 다형성 또는 단지 구성을 원하는지 결정하는 것은 당신에게 달려 있습니다.

+0

Pimpl의 래퍼 클래스는 비 가상 메서드를 가져야합니다.이 경우 라이브러리 클래스의 가상 메서드를 숨기는 데 정확히 사용됩니다. 가상 메소드가 라이브러리 인터페이스에 존재한다면, 바이너리 호환을 유지하면서 더 많은 가상 메소드로 라이브러리 인터페이스를 새로운 버전으로 확장하는 것을 불가능하게 만들 것입니다. 하지만 게시 된 인터페이스가 가상이 아닌 경우 클라이언트가 어떻게 서브 클래 싱합니까? 그러므로 포스트. – shojtsy

+1

좋아요, 그럼 당신의 요지를 이해합니다. 하지만이 시점에서 Pimpl의 문제는 아닙니다. 인터페이스에서'virtual' 메소드의 사용에 관한 더 많은 문제. –

+0

"Derived에서 파생 된 파생물을 다시 작성할 수 있도록 파생 된 파생물을 재 선언해야합니다. 아니요, 재정의 된 가상 메소드는 암시 적으로 가상입니다. –

0

헤더 파일에서 PImpl 클래스를 노출하면이 클래스에서 상속 할 수 있습니다. 외부 클래스에는 PImpl 객체에 대한 포인터가 있기 때문에 역방향 이식성을 유지할 수 있습니다. 물론 라이브러리의 클라이언트 코드가 현명하지 않으면 노출 된 PImpl 객체를 오용하여 바이너리 역 호환성을 망칠 수 있습니다. 일부 메모를 추가하여 PImpl의 헤더 파일에서 사용자에게 경고 할 수 있습니다.

관련 문제