2008-09-19 40 views

Trả lời

92

Chúng có thể xảy ra nếu bạn cố thực hiện cuộc gọi hàm ảo từ một hàm tạo hoặc hàm hủy. Vì bạn không thể thực hiện cuộc gọi hàm ảo từ một hàm tạo hoặc hàm hủy (đối tượng lớp dẫn xuất chưa được xây dựng hoặc đã bị hủy), nó gọi phiên bản lớp cơ sở, trong trường hợp của hàm ảo thuần túy, không không tồn tại.

(Xem demo sống here)

class Base 
{ 
public: 
    Base() { doIt(); } // DON'T DO THIS 
    virtual void doIt() = 0; 
}; 

void Base::doIt() 
{ 
    std::cout<<"Is it fine to call pure virtual function from constructor?"; 
} 

class Derived : public Base 
{ 
    void doIt() {} 
}; 

int main(void) 
{ 
    Derived d; // This will cause "pure virtual function call" error 
} 
+2

Bất kỳ lý do nào tại sao trình biên dịch không thể nắm bắt được điều này, nói chung? – Thomas

+0

Tôi không thấy bất kỳ lý do kỹ thuật nào tại sao trình biên dịch không thể bắt được điều này. –

+1

GCC chỉ cho tôi một cảnh báo: test.cpp: Trong hàm khởi tạo 'Cơ sở :: Cơ sở()': test.cpp: 4: cảnh báo: trừu tượng ảo 'virtual void Base :: doIt()' được gọi từ hàm tạo Nhưng nó không thành công tại thời gian liên kết. – Thomas

0

Tôi đoán có một vtbl được tạo cho lớp trừu tượng vì một số lý do bên trong (có thể cần thiết cho một số loại thông tin thời gian chạy) và có sự cố và một đối tượng thực nhận được nó. Đó là một lỗi. Điều đó một mình nên nói rằng một cái gì đó mà không thể xảy ra được.

đầu cơ tinh khiết

chỉnh sửa: trông giống như tôi đã sai trong trường hợp trong câu hỏi. OTOH IIRC một số ngôn ngữ không cho phép các cuộc gọi vtbl ra khỏi trình phá hủy hàm tạo.

+0

Nó không phải là lỗi trong trình biên dịch, nếu đó là ý của bạn. – Thomas

+0

Nghi ngờ của bạn là đúng - C# và Java cho phép điều này. Trong những ngôn ngữ đó, bohjects đang được xây dựng có loại cuối cùng của chúng. Trong C++, các đối tượng thay đổi kiểu trong khi xây dựng và đó là lý do tại sao và khi bạn có thể có các đối tượng với một kiểu trừu tượng. – MSalters

+0

* TẤT CẢ * các lớp trừu tượng và các đối tượng thực được tạo ra bắt nguồn từ chúng, cần một vtbl (bảng chức năng ảo), liệt kê các hàm ảo nào sẽ được gọi trên đó. Trong C++, một đối tượng chịu trách nhiệm tạo các thành viên của riêng nó, bao gồm cả bảng chức năng ảo. Các trình xây dựng được gọi từ lớp cơ sở đến dẫn xuất, và các trình phá hủy được gọi là bắt nguồn từ lớp cơ sở, vì vậy trong một lớp cơ sở trừu tượng, bảng chức năng ảo chưa có sẵn. – fuzzyTew

7

Thông thường khi bạn gọi một hàm ảo thông qua một con trỏ tòn ten - rất có thể các trường hợp đã bị phá hủy.

Cũng có thể có nhiều lý do "sáng tạo" hơn: có thể bạn đã quản lý để cắt bỏ phần đối tượng của bạn nơi chức năng ảo được triển khai. Nhưng thường chỉ là trường hợp đã bị phá hủy.

-1

Đây là một cách lén lút để nó xảy ra. Tôi đã có điều này về cơ bản xảy ra với tôi ngày hôm nay.

class A 
{ 
    A *pThis; 
    public: 
    A() 
    : pThis(this) 
    { 
    } 

    void callFoo() 
    { 
    pThis->foo(); // call through the pThis ptr which was initialized in the constructor 
    } 

    virtual void foo() = 0; 
}; 

class B : public A 
{ 
public: 
    virtual void foo() 
    { 
    } 
}; 

B b(); 
b.callFoo(); 
+1

Ít nhất nó không thể được sao chép trên vc2008 của tôi, vptr không trỏ đến A của vtable khi lần đầu tiên được khởi tạo trong A contructor, nhưng sau đó khi B được khởi tạo đầy đủ, vptr được thay đổi để trỏ đến B's vtable, đó là ok –

+0

coudnt Tái hiện nó với vs2010/12 – makc

+0

* 'Tôi đã có điều này về cơ bản xảy ra với tôi hôm nay' * rõ ràng là không đúng, bởi vì đơn giản là sai: một hàm ảo thuần khiết chỉ được gọi khi' callFoo() 'được gọi bên trong một hàm tạo (hoặc destructor) , bởi vì tại thời điểm này đối tượng vẫn còn (hoặc đã) ở giai đoạn A. [Đây là một phiên bản đang chạy] (https://ideone.com/5zi4Kc) của mã của bạn mà không có lỗi cú pháp trong 'B b();' - dấu ngoặc đơn làm cho nó trở thành một khai báo hàm, bạn muốn một đối tượng. – Wolf

60

Cũng như trường hợp tiêu chuẩn gọi một hàm ảo từ các nhà xây dựng hoặc destructor của một đối tượng với chức năng ảo tinh khiết bạn cũng có thể có được một hàm ảo gọi tinh khiết (trên MSVC ít nhất) nếu bạn gọi một ảo chức năng sau khi đối tượng đã bị hủy. Rõ ràng đây là một điều khá xấu để thử và làm, nhưng nếu bạn đang làm việc với các lớp trừu tượng như các giao diện và bạn mess up thì đó là một cái gì đó mà bạn có thể nhìn thấy. Nó có thể có nhiều khả năng nếu bạn đang sử dụng giao diện tính tham chiếu và bạn có một lỗi đếm ref hoặc nếu bạn có một đối tượng sử dụng/đối tượng phá hủy cuộc đua điều kiện trong một chương trình đa luồng ... Điều về các loại purecall là nó thường ít dễ dàng để hiểu được những gì đang xảy ra như là một kiểm tra cho các 'nghi phạm thông thường' của các cuộc gọi ảo trong ctor và dtor sẽ đi lên sạch sẽ.

Để giúp gỡ lỗi các loại vấn đề này, bạn có thể, trong các phiên bản khác nhau của MSVC, thay thế trình xử lý trong suốt của thư viện thời gian chạy. Bạn thực hiện việc này bằng cách cung cấp chức năng của riêng bạn với chữ ký này:

int __cdecl _purecall(void) 

và liên kết nó trước khi bạn liên kết thư viện thời gian chạy. Điều này cho phép bạn kiểm soát những gì xảy ra khi một purecall được phát hiện. Một khi bạn có quyền kiểm soát, bạn có thể làm điều gì đó hữu ích hơn trình xử lý tiêu chuẩn. Tôi có một người xử lý có thể cung cấp một dấu vết ngăn xếp của nơi mà các purecall đã xảy ra; xem tại đây: http://www.lenholgate.com/blog/2006/01/purecall.html để biết thêm chi tiết.

(Lưu ý bạn cũng có thể gọi _set_purecall_handler() để cài đặt trình xử lý của bạn trong một số phiên bản của MSVC).

+0

Cảm ơn con trỏ về việc nhận lời gọi _purecall() trên một cá thể đã xóa; Tôi đã không nhận thức được điều đó, nhưng chỉ chứng minh nó với bản thân mình với một mã kiểm tra nhỏ. Tôi nghĩ rằng tôi đã đối phó với một cuộc đua mà một sợi khác đang cố gắng sử dụng một đối tượng có nguồn gốc trước khi nó được xây dựng hoàn chỉnh, nhưng điều này làm sáng tỏ một vấn đề mới và dường như phù hợp hơn với bằng chứng. –

+0

Một thứ khác tôi sẽ thêm: lời gọi '_purecall()' thường xảy ra khi gọi một phương thức của một cá thể đã xóa sẽ _not_ xảy ra nếu lớp cơ sở đã được khai báo với '__declspec (novtable)' optimization (Microsoft specific) . Với điều đó, hoàn toàn có thể gọi một phương thức ảo đã ghi đè sau khi đối tượng đã bị xóa, có thể che giấu vấn đề cho đến khi nó cắn bạn vào một dạng khác. Cái bẫy '_purecall()' là bạn của bạn! –

+0

Điều đó rất hữu ích khi biết Dave, tôi đã nhìn thấy một vài tình huống gần đây, nơi tôi không nhận được mật khẩu khi tôi nghĩ mình nên như vậy. Có lẽ tôi đã rơi hôi của tối ưu hóa đó. –

0

Tôi sử dụng VS2010 và bất cứ khi nào tôi thử gọi hàm hủy trực tiếp từ phương thức công khai, tôi nhận được lỗi "gọi hàm ảo thuần túy" trong thời gian chạy.

template <typename T> 
class Foo { 
public: 
    Foo<T>() {}; 
    ~Foo<T>() {}; 

public: 
    void SomeMethod1() { this->~Foo(); }; /* ERROR */ 
}; 

Vì vậy, tôi di chuyển những gì bên trong ~ Foo() để tách phương thức riêng tư, sau đó nó hoạt động như một sự quyến rũ.

template <typename T> 
class Foo { 
public: 
    Foo<T>() {}; 
    ~Foo<T>() {}; 

public: 
    void _MethodThatDestructs() {}; 
    void SomeMethod1() { this->_MethodThatDestructs(); }; /* OK */ 
}; 
Các vấn đề liên quan