Tham chiếu không xác định đến vtable


357

Khi xây dựng chương trình C ++ của tôi, tôi nhận được thông báo lỗi

tham chiếu không xác định đến 'vtable ...

Nguyên nhân của vấn đề này là gì? Làm thế nào để tôi sửa nó?


Điều đó xảy ra khi tôi nhận được lỗi cho đoạn mã sau (Lớp đang nói đến là CGameModule.) Và tôi không thể hiểu được vấn đề là gì. Lúc đầu, tôi nghĩ nó có liên quan đến việc quên cung cấp một chức năng ảo cho cơ thể, nhưng theo tôi hiểu, mọi thứ đều ở đây. Chuỗi thừa kế hơi dài, nhưng đây là mã nguồn liên quan. Tôi không chắc chắn những thông tin khác tôi nên cung cấp.

Lưu ý: Hàm tạo là nơi xảy ra lỗi này.

Mã của tôi:

class CGameModule : public CDasherModule {
 public:
  CGameModule(Dasher::CEventHandler *pEventHandler, CSettingsStore *pSettingsStore, CDasherInterfaceBase *pInterface, ModuleID_t iID, const char *szName)
  : CDasherModule(pEventHandler, pSettingsStore, iID, 0, szName)
  { 
      g_pLogger->Log("Inside game module constructor");   
      m_pInterface = pInterface; 
  }

  virtual ~CGameModule() {};

  std::string GetTypedTarget();

  std::string GetUntypedTarget();

  bool DecorateView(CDasherView *pView) {
      //g_pLogger->Log("Decorating the view");
      return false;
  }

  void SetDasherModel(CDasherModel *pModel) { m_pModel = pModel; }


  virtual void HandleEvent(Dasher::CEvent *pEvent); 

 private:



  CDasherNode *pLastTypedNode;


  CDasherNode *pNextTargetNode;


  std::string m_sTargetString;


  size_t m_stCurrentStringPos;


  CDasherModel *m_pModel;


  CDasherInterfaceBase *m_pInterface;
};

Kế thừa từ...

class CDasherModule;
typedef std::vector<CDasherModule*>::size_type ModuleID_t;

/// \ingroup Core
/// @{
class CDasherModule : public Dasher::CDasherComponent {
 public:
  CDasherModule(Dasher::CEventHandler * pEventHandler, CSettingsStore * pSettingsStore, ModuleID_t iID, int iType, const char *szName);

  virtual ModuleID_t GetID();
  virtual void SetID(ModuleID_t);
  virtual int GetType();
  virtual const char *GetName();

  virtual bool GetSettings(SModuleSettings **pSettings, int *iCount) {
    return false;
  };

 private:
  ModuleID_t m_iID;
  int m_iType;
  const char *m_szName;
};

Mà kế thừa từ ....

namespace Dasher {
  class CEvent;
  class CEventHandler;
  class CDasherComponent;
};

/// \ingroup Core
/// @{
class Dasher::CDasherComponent {
 public:
  CDasherComponent(Dasher::CEventHandler* pEventHandler, CSettingsStore* pSettingsStore);
  virtual ~CDasherComponent();

  void InsertEvent(Dasher::CEvent * pEvent);
  virtual void HandleEvent(Dasher::CEvent * pEvent) {};

  bool GetBoolParameter(int iParameter) const;
  void SetBoolParameter(int iParameter, bool bValue) const;

  long GetLongParameter(int iParameter) const;
  void SetLongParameter(int iParameter, long lValue) const;

  std::string GetStringParameter(int iParameter) const;
  void        SetStringParameter(int iParameter, const std::string & sValue) const;

  ParameterType   GetParameterType(int iParameter) const;
  std::string     GetParameterName(int iParameter) const;

 protected:
  Dasher::CEventHandler *m_pEventHandler;
  CSettingsStore *m_pSettingsStore;
};
/// @}


#endif

Hàm nào đang ném "tham chiếu không xác định đến vtable ..."?
J. Polfer

3
Tôi hoàn toàn bỏ lỡ rằng thông báo lỗi chỉ định một chức năng. Nó tình cờ là người xây dựng, vì vậy tôi thấy tên lớp của mình và không tạo được kết nối. Vì vậy, các nhà xây dựng đang ném này. Tôi sẽ thêm chi tiết đó vào bài viết gốc của mình.
RyanG

3
Nếu bạn chưa xây dựng lại các tệp dự án của mình sau khi thực hiện các thay đổi quan trọng (ví dụ qmake -projectvà sau đó qmake) để tạo mới Makefile, đó có thể là nguồn gây ra lỗi khi sử dụng Qt.
David C. Rankin

@ DavidC.Rankin, một vấn đề khác liên quan đến Qt là nếu tệp với Q_OBJECTđược sao chép bên ngoài, nhưng chưa phải là một phần của tệp .pro, thì mặc dù nó biên dịch tốt, nhưng nó không liên kết. Chúng ta phải thêm .h/.cpptệp đó vào tệp .pro để có thể qmake.
iammilind

Câu trả lời:


420

Câu hỏi thường gặp về GCC có một mục trên đó:

Giải pháp là đảm bảo rằng tất cả các phương thức ảo không thuần túy được xác định. Lưu ý rằng một hàm hủy phải được xác định ngay cả khi nó được khai báo thuần ảo [class.dtor] / 7.


17
nm -C CGameModule.o | grep CGameModule::sẽ liệt kê các phương thức được định nghĩa, giả sử toàn bộ lớp thực hiện của bạn đi vào tệp đối tượng logic. Bạn có thể so sánh với những gì được xác định là ảo để tìm ra những gì bạn đã bỏ lỡ.
Troy Daniels

132
FFS, tại sao trình biên dịch không kiểm tra điều đó và in một lỗi lộn xộn?
Lenar Hoyt

20
Rõ ràng, điều này chỉ có thể được phát hiện bởi trình liên kết, không phải trình biên dịch.
Xoph

2
Trong trường hợp của tôi, chúng ta đã có lớp trừu tượng không có triển khai hàm hủy. Tôi đã phải thực hiện triển khai trống ~ MyClass () {}
Shefy Gur-ary

1
Bạn có thể gặp lỗi như thế này khi các đối tượng bạn đang cố liên kết bị thiếu trong kho lưu trữ (tệp libxyz.a): tham chiếu không xác định đến `vtable cho objfilename '
Kemin Zhou

162

Đối với những gì nó có giá trị, việc quên một cơ thể trên một hàm hủy ảo sẽ tạo ra như sau:

tham chiếu không xác định đến `vtable cho CYourClass '.

Tôi đang thêm một ghi chú vì thông báo lỗi là lừa đảo. (Đây là phiên bản gcc 4.6.3.)


23
Tôi đã phải đặt rõ ràng phần thân của hàm hủy ảo trống của mình vào tệp định nghĩa (* .cc). Có nó trong tiêu đề vẫn cho tôi lỗi.
PopcornKing

4
Lưu ý rằng một khi tôi đã thêm hàm hủy ảo vào tệp thực thi, thì gcc đã cho tôi biết lỗi thực tế, đó là phần thân bị thiếu trên một chức năng khác.
psychboom 16/07/2015

1
@PopcornKing Mình thấy vấn đề tương tự. Ngay cả việc xác định ~Destructor = default;trong tệp tiêu đề cũng không giúp được gì. Có một lỗi tài liệu nộp chống lại gcc?
RD

đây có thể là một vấn đề khác, nhưng vấn đề của tôi chỉ là không có triển khai cho một hàm hủy không ảo (đã chuyển sang các con trỏ duy nhất / được chia sẻ và xóa nó khỏi tệp nguồn, nhưng không có "triển khai" trong tiêu đề )
svenevs

điều này đã giải quyết vấn đề của tôi, việc thêm một thân {} trống cho hàm hủy ảo đã tránh được lỗi.
Bogdan Ionitza

56

Vì vậy, tôi đã tìm ra vấn đề và đó là sự kết hợp của logic xấu và không hoàn toàn quen thuộc với thế giới tự động / tự động. Tôi đã thêm các tệp chính xác vào mẫu Makefile.am của mình, nhưng tôi không chắc bước nào trong quy trình xây dựng của chúng tôi thực sự tạo ra tệp Makefile. Vì vậy, tôi đã biên dịch với một tệp makefile cũ mà không biết gì về các tệp mới của tôi.

Cảm ơn câu trả lời và liên kết đến Câu hỏi thường gặp của GCC. Tôi chắc chắn sẽ đọc rằng để tránh vấn đề này xảy ra vì một lý do thực sự.


43
Tóm lại: .cpp chỉ không được bao gồm trong bản dựng. Thông báo lỗi thực sự sai lệch.
Offirmo

67
Đối với người dùng Qt: bạn có thể gặp lỗi tương tự nếu bạn quên moc một tiêu đề.
Chris Mor Trước

8
Tôi nghĩ bạn nên chấp nhận câu trả lời của Alexandre Hamez. Những người tìm kiếm lỗi này rất có thể cần giải pháp của anh ấy thay vì của bạn.
Tim

13
-1 Đây có thể là giải pháp cho vấn đề của bạn, nhưng nó không phải là câu trả lời cho câu hỏi ban đầu. Câu trả lời đúng chỉ đơn giản là bạn đã không cung cấp một tệp đối tượng với các ký hiệu được yêu cầu. Tại sao bạn không cung cấp cho họ là một câu chuyện khác.
Walter

12
@Walter: Thật ra đây là câu trả lời chính xác mà tôi đang tìm kiếm. Những người khác là rõ ràng, và do đó không có ích.
Edgar Bonet

50

Nếu bạn đang sử dụng Qt, hãy thử chạy lại qmake. Nếu lỗi này thuộc lớp của widget, qmake có thể đã không nhận thấy rằng vtable lớp ui nên được tạo lại. Điều này đã khắc phục vấn đề cho tôi.


2
Tôi vừa xóa toàn bộ thư mục với bản dựng nó cũng hoạt động.
Tomáš Zato - Phục hồi Monica

Điều này không phải là duy nhất qmake, tôi cũng như vậy cmake. Một phần cho vấn đề có thể là cả hai công cụ có một chút vấn đề với các tệp tiêu đề, điều này có thể không phải lúc nào cũng kích hoạt việc xây dựng lại khi cần.
MSalters

2
Nghĩ rằng "Rebuild" reran qmake tự động ... dường như không. Tôi đã thực hiện "Chạy qmake" theo đề nghị của bạn, sau đó "Xây dựng lại" và nó đã khắc phục vấn đề của tôi.
yano

45

Tham chiếu không xác định đến vtable cũng có thể xảy ra do tình huống sau đây. Hãy thử điều này:

Lớp A chứa:

virtual void functionA(parameters)=0; 
virtual void functionB(parameters);

Lớp B chứa:

  1. Định nghĩa cho hàm trênA.
  2. Định nghĩa cho hàm trênB.

Lớp C chứa: Bây giờ bạn đang viết Lớp C trong đó bạn sẽ lấy nó từ Lớp A.

Bây giờ nếu bạn cố gắng biên dịch, bạn sẽ nhận được tham chiếu Không xác định cho vtable cho Class C là lỗi.

Lý do:

functionA được định nghĩa là ảo thuần và định nghĩa của nó được cung cấp trong Lớp B. functionB được định nghĩa là ảo (KHÔNG PHẢI VIRTUAL) vì vậy nó cố gắng tìm định nghĩa của nó trong chính Lớp A nhưng bạn đã cung cấp định nghĩa của nó trong Lớp B.

Giải pháp:

  1. Tạo hàm B dưới dạng ảo thuần túy (nếu bạn có yêu cầu như vậy) virtual void functionB(parameters) =0; (Công việc này được kiểm tra)
  2. Cung cấp Định nghĩa cho hàmB trong Lớp A, giữ cho nó là ảo. (Hy vọng nó hoạt động khi tôi không thử điều này)

@ ilya1725 Chỉnh sửa được đề xuất của bạn không chỉ là sửa định dạng và tương tự, bạn cũng đang thay đổi câu trả lời, ví dụ như nói rằng lớp C có nguồn gốc từ B thay vì A và bạn đang thay đổi giải pháp thứ hai. Điều này thay đổi đáng kể câu trả lời. Trong những trường hợp này, xin vui lòng để lại một bình luận cho tác giả thay thế. Cảm ơn bạn!
Fabio nói Phục hồi lại

@FabioTurati lớp nào được classC kế thừa từ đó? Câu không rõ ràng. Ngoài ra, ý nghĩa của "Lớp C chứa:" là gì?
ilya1725

@ ilya1725 Câu trả lời này không rõ ràng lắm và tôi không chống lại việc chỉnh sửa và cải thiện nó. Điều tôi đang nói là bản chỉnh sửa của bạn thay đổi ý nghĩa của câu trả lời và đó là một sự thay đổi quá lớn. Hy vọng, tác giả sẽ bước vào và làm rõ ý anh ta (mặc dù anh ta đã không hoạt động trong một thời gian dài).
Fabio nói Phục hồi lại

Cảm ơn! Trong trường hợp của tôi, tôi chỉ có 2 lớp trong hệ thống phân cấp của mình. Lớp A khai báo một phương thức ảo thuần túy. Tuyên bố của lớp B tuyên bố rằng nó sẽ ghi đè phương thức này, nhưng tôi chưa viết định nghĩa của phương thức overriden.
Nick Desaulniers

44

Tôi chỉ đơn giản là đã gặp lỗi này vì tệp cpp của tôi không có trong tệp thực hiện.


Thật vậy, dường như thông điệp thay đổi một chút so với thông thường undefined reference to {function/class/struct}khi có virtualnhững thứ liên quan. Ném tôi đi.
Keith M

30

Một vtable

Có thể hữu ích khi biết thông báo lỗi đang nói về điều gì trước khi cố gắng sửa nó. Tôi sẽ bắt đầu ở cấp độ cao, sau đó tìm hiểu thêm một số chi tiết. Bằng cách đó mọi người có thể bỏ qua phía trước một khi họ cảm thấy thoải mái với sự hiểu biết của họ về vtables. Sôi và có một đám người bỏ qua ngay bây giờ. :) Đối với những người gắn bó xung quanh:

Một vtable về cơ bản là triển khai đa hình phổ biến nhất trong C ++ . Khi vtables được sử dụng, mọi lớp đa hình đều có vtable ở đâu đó trong chương trình; bạn có thể nghĩ về nó như một staticthành viên dữ liệu (ẩn) của lớp. Mỗi đối tượng của một lớp đa hình được liên kết với vtable cho lớp có nguồn gốc nhất của nó. Bằng cách kiểm tra sự liên kết này, chương trình có thể hoạt động phép thuật đa hình của nó. Thông báo quan trọng: một vtable là một chi tiết thực hiện. Nó không bắt buộc theo tiêu chuẩn C ++, mặc dù hầu hết (tất cả?) Trình biên dịch C ++ sử dụng vtables để thực hiện hành vi đa hình. Các chi tiết tôi đang trình bày là cách tiếp cận điển hình hoặc hợp lý. Trình biên dịch được phép đi chệch khỏi điều này!

Mỗi đối tượng đa hình có một con trỏ (ẩn) đến vtable cho lớp có nguồn gốc nhất của đối tượng (có thể là nhiều con trỏ, trong các trường hợp phức tạp hơn). Bằng cách nhìn vào con trỏ, chương trình có thể cho biết loại "thực" của một đối tượng là gì (ngoại trừ trong quá trình xây dựng, nhưng hãy bỏ qua trường hợp đặc biệt đó). Ví dụ, nếu một đối tượng kiểu Akhông trỏ đến vtable của A, thì đối tượng đó thực sự là một đối tượng phụ của một cái gì đó có nguồn gốc từ A.

Cái tên "vtable" xuất phát từ " v chức năng irtual bảng ". Đó là một bảng lưu trữ các con trỏ tới các hàm (ảo). Một trình biên dịch chọn quy ước của nó về cách đặt bảng; một cách tiếp cận đơn giản là đi qua các hàm ảo theo thứ tự chúng được khai báo trong các định nghĩa lớp. Khi một hàm ảo được gọi, chương trình theo con trỏ của đối tượng đến vtable, đi đến mục được liên kết với hàm mong muốn, sau đó sử dụng con trỏ hàm được lưu trữ để gọi đúng hàm. Có nhiều thủ thuật khác nhau để thực hiện công việc này, nhưng tôi sẽ không đi sâu vào những vấn đề ở đây.

Ở đâu / khi nào được vtabletạo ra?

Trình biên dịch vtable được tạo tự động (đôi khi được gọi là "phát ra") bởi trình biên dịch. Một trình biên dịch có thể phát ra một vtable trong mọi đơn vị dịch thuật nhìn thấy định nghĩa lớp đa hình, nhưng điều đó thường sẽ là quá mức cần thiết. Một cách khác ( được sử dụng bởi gcc và có thể bởi những người khác) là chọn một đơn vị dịch thuật để đặt vtable, tương tự như cách bạn sẽ chọn một tệp nguồn duy nhất để đặt các thành viên dữ liệu tĩnh của lớp. Nếu quá trình lựa chọn này không chọn được bất kỳ đơn vị dịch thuật nào, thì vtable trở thành một tham chiếu không xác định. Do đó, lỗi, thông báo được thừa nhận là không đặc biệt rõ ràng.

Tương tự, nếu quá trình lựa chọn không chọn một đơn vị dịch, nhưng tệp đối tượng đó không được cung cấp cho trình liên kết, thì vtable trở thành một tham chiếu không xác định. Thật không may, thông báo lỗi có thể thậm chí còn ít rõ ràng hơn trong trường hợp này so với trường hợp quá trình lựa chọn thất bại. (Cảm ơn những người trả lời đã đề cập đến khả năng này. Tôi có lẽ đã quên nó nếu không.)

Quá trình lựa chọn được sử dụng bởi gcc có ý nghĩa nếu chúng ta bắt đầu với truyền thống dành một tệp nguồn (duy nhất) cho mỗi lớp cần một để thực hiện. Sẽ thật tuyệt khi phát ra vtable khi biên dịch tệp nguồn đó. Hãy gọi đó là mục tiêu của chúng tôi. Tuy nhiên, quá trình lựa chọn cần phải hoạt động ngay cả khi truyền thống này không được tuân theo. Vì vậy, thay vì tìm kiếm việc thực hiện toàn bộ lớp, chúng ta hãy tìm kiếm việc thực hiện một thành viên cụ thể của lớp. Nếu truyền thống được tiếp nối - và nếu thành viên đó thực tế được thực hiện - thì điều này đạt được mục tiêu.

Thành viên được chọn bởi gcc (và có khả năng bởi các trình biên dịch khác) là hàm ảo không nội tuyến đầu tiên không phải là ảo thuần túy. Nếu bạn là một phần của đám đông tuyên bố các hàm tạo và hàm hủy trước các hàm thành viên khác, thì hàm hủy đó có cơ hội được chọn. (Bạn đã nhớ làm cho hàm hủy ảo, phải không?) Có các trường hợp ngoại lệ; Tôi hy vọng rằng các ngoại lệ phổ biến nhất là khi định nghĩa nội tuyến được cung cấp cho hàm hủy và khi yêu cầu hủy mặc định (sử dụng " = default").

Người thông minh có thể nhận thấy rằng một lớp đa hình được phép cung cấp các định nghĩa nội tuyến cho tất cả các chức năng ảo của nó. Điều đó không làm cho quá trình lựa chọn thất bại? Nó không có trong trình biên dịch cũ. Tôi đã đọc rằng các trình biên dịch mới nhất đã giải quyết tình huống này, nhưng tôi không biết số phiên bản có liên quan. Tôi có thể thử tìm kiếm cái này, nhưng nó dễ dàng hơn để viết mã xung quanh nó hoặc đợi trình biên dịch phàn nàn.

Tóm lại, có ba nguyên nhân chính gây ra lỗi "không xác định tham chiếu đến vtable":

  1. Một hàm thành viên bị thiếu định nghĩa của nó.
  2. Một tệp đối tượng không được liên kết.
  3. Tất cả các chức năng ảo có định nghĩa nội tuyến.

Những nguyên nhân này tự chúng không đủ để gây ra lỗi. Thay vào đó, đây là những gì bạn sẽ giải quyết để khắc phục lỗi. Đừng hy vọng rằng việc cố tình tạo một trong những tình huống này chắc chắn sẽ tạo ra lỗi này; Có những yêu cầu khác. Đừng hy vọng rằng việc giải quyết các tình huống này sẽ giải quyết lỗi này.

(OK, số 3 có thể đã đủ khi câu hỏi này được hỏi.)

Làm thế nào để sửa lỗi?

Chào mừng trở lại mọi người bỏ qua phía trước! :)

  1. Nhìn vào định nghĩa lớp học của bạn. Tìm hàm ảo không nội tuyến đầu tiên không phải là thuần ảo (không phải " = 0") và định nghĩa bạn cung cấp (không phải " = default").
    • Nếu không có chức năng như vậy, hãy thử sửa đổi lớp của bạn để có một cái. (Lỗi có thể được giải quyết.)
    • Xem thêm câu trả lời của Philip Thomas cho một cảnh báo.
  2. Tìm định nghĩa cho hàm đó. Nếu nó bị thiếu, thêm nó! (Lỗi có thể được giải quyết.)
  3. Kiểm tra lệnh liên kết của bạn. Nếu nó không đề cập đến tệp đối tượng với định nghĩa của hàm đó, hãy sửa nó! (Lỗi có thể được giải quyết.)
  4. Lặp lại các bước 2 và 3 cho từng chức năng ảo, sau đó cho từng chức năng không ảo, cho đến khi lỗi được giải quyết. Nếu bạn vẫn bị kẹt, lặp lại cho từng thành viên dữ liệu tĩnh.

Ví dụ
Chi tiết về những việc cần làm có thể khác nhau và đôi khi phân nhánh thành các câu hỏi riêng biệt (như Tham chiếu không xác định / lỗi biểu tượng bên ngoài chưa được giải quyết là gì và làm cách nào để khắc phục? ). Tuy nhiên, tôi sẽ cung cấp một ví dụ về những việc cần làm trong một trường hợp cụ thể có thể làm rối loạn các lập trình viên mới hơn.

Bước 1 đề cập đến việc sửa đổi lớp của bạn để nó có chức năng của một loại nhất định. Nếu mô tả về chức năng đó đi qua đầu bạn, bạn có thể đang ở trong tình huống tôi dự định giải quyết. Hãy nhớ rằng đây là một cách để hoàn thành mục tiêu; đó không phải là cách duy nhất và dễ dàng có thể có những cách tốt hơn trong tình huống cụ thể của bạn. Hãy gọi lớp của bạn A. Là hàm hủy của bạn được khai báo (theo định nghĩa lớp của bạn) là một trong hai

virtual ~A() = default;

hoặc là

virtual ~A() {}

? Nếu vậy, hai bước sẽ thay đổi hàm hủy của bạn thành loại hàm chúng ta muốn. Đầu tiên, thay đổi dòng đó thành

virtual ~A();

Thứ hai, đặt dòng sau vào tệp nguồn là một phần của dự án của bạn (tốt nhất là tệp có triển khai lớp, nếu bạn có):

A::~A() {}

Điều đó làm cho hàm hủy (ảo) của bạn không nội tuyến và không được tạo bởi trình biên dịch. (Hãy thoải mái sửa đổi mọi thứ để phù hợp hơn với phong cách định dạng mã của bạn, chẳng hạn như thêm nhận xét tiêu đề vào định nghĩa hàm.)


Ôi, bravo! Đối với các giải thích rất chi tiết và rất tốt.
David C. Rankin

Phải cuộn xuống cách xa để đọc cái này. Có một upvote cho lời giải thích tuyệt vời!
Thomas

24

Có rất nhiều suy đoán đang diễn ra trong các câu trả lời khác nhau ở đây. Dưới đây tôi sẽ đưa ra một mã khá tối thiểu để tái tạo lỗi này và giải thích lý do tại sao nó xảy ra.

Mã tối thiểu để tái tạo lỗi này

IBase.hpp

#pragma once

class IBase {
    public:
        virtual void action() = 0;
};

Xuất phát.hpp

#pragma once

#include "IBase.hpp"

class Derived : public IBase {
    public:
        Derived(int a);
        void action() override;
};

Derogen.cpp

#include "Derived.hpp"
Derived::Derived(int a) { }
void Derived::action() {}

mygroup.cpp

#include <memory>
#include "Derived.hpp"

class MyClass {

    public:
        MyClass(std::shared_ptr<Derived> newInstance) : instance(newInstance) {

        }

        void doSomething() {
            instance->action();
        }

    private:
        std::shared_ptr<Derived> instance;
};

int main(int argc, char** argv) {
    Derived myInstance(5);
    MyClass c(std::make_shared<Derived>(myInstance));
    c.doSomething();
    return 0;
}

Bạn có thể biên dịch cái này bằng GCC như thế này:

g++ -std=c++11 -o a.out myclass.cpp Derived.cpp

Bây giờ bạn có thể tái tạo lỗi bằng cách xóa = 0trong IBase.hpp. Tôi nhận được lỗi này:

~/.../catkin_ws$ g++ -std=c++11 -o /tmp/m.out /tmp/myclass.cpp /tmp/Derived.cpp
/tmp/cclLscB9.o: In function `IBase::IBase(IBase const&)':
myclass.cpp:(.text._ZN5IBaseC2ERKS_[_ZN5IBaseC5ERKS_]+0x13): undefined reference to `vtable for IBase'
/tmp/cc8Smvhm.o: In function `IBase::IBase()':
Derived.cpp:(.text._ZN5IBaseC2Ev[_ZN5IBaseC5Ev]+0xf): undefined reference to `vtable for IBase'
/tmp/cc8Smvhm.o:(.rodata._ZTI7Derived[_ZTI7Derived]+0x10): undefined reference to `typeinfo for IBase'
collect2: error: ld returned 1 exit status

Giải trình

Lưu ý rằng mã ở trên không yêu cầu bất kỳ hàm hủy ảo, hàm tạo hoặc bất kỳ tệp bổ sung nào khác để biên dịch thành công (mặc dù bạn nên có chúng).

Cách hiểu lỗi này như sau: Linker đang tìm kiếm hàm tạo của IBase. Điều này sẽ cần nó cho các nhà xây dựng của Deriving. Tuy nhiên, khi Derogen ghi đè các phương thức từ IBase, nó có vtable kèm theo nó sẽ tham chiếu IBase. Khi trình liên kết nói "tham chiếu không xác định đến vtable cho IBase", về cơ bản có nghĩa là Derive có tham chiếu vtable với IBase nhưng nó không thể tìm thấy bất kỳ mã đối tượng được biên dịch nào của IBase để tìm kiếm. Vì vậy, điểm mấu chốt là lớp IBase có khai báo mà không cần triển khai. Điều này có nghĩa là một phương thức trong IBase được khai báo là ảo nhưng chúng tôi quên đánh dấu nó là ảo thuần HOẶC cung cấp định nghĩa của nó.

Mẹo chia tay

Nếu tất cả các cách khác đều thất bại thì một cách để gỡ lỗi lỗi này là xây dựng chương trình tối thiểu biên dịch và sau đó tiếp tục thay đổi để nó đến trạng thái bạn muốn. Ở giữa, tiếp tục biên dịch để xem khi nào nó bắt đầu thất bại.

Lưu ý về hệ thống xây dựng ROS và Catkin

Nếu bạn đã biên dịch tập hợp các lớp trong ROS bằng hệ thống xây dựng catkin thì bạn sẽ cần các dòng sau trong CMakeLists.txt:

add_executable(myclass src/myclass.cpp src/Derived.cpp)
add_dependencies(myclass theseus_myclass_cpp)
target_link_libraries(myclass ${catkin_LIBRARIES})

Dòng đầu tiên về cơ bản nói rằng chúng tôi muốn tạo một tệp thực thi có tên mygroup và mã để xây dựng tệp này có thể được tìm thấy các tệp theo sau. Một trong những tệp này nên có hàm main (). Lưu ý rằng bạn không phải chỉ định tệp .hpp ở bất kỳ đâu trong CMakeLists.txt. Ngoài ra, bạn không phải chỉ định Deriving.cpp là thư viện.


18

Tôi vừa gặp phải một nguyên nhân khác cho lỗi này mà bạn có thể kiểm tra.

Lớp cơ sở đã định nghĩa một hàm ảo thuần túy là:

virtual int foo(int x = 0);

Và lớp con có

int foo(int x) override;

Vấn đề là lỗi đánh máy "=0"được cho là nằm ngoài dấu ngoặc đơn:

virtual int foo(int x) = 0;

Vì vậy, trong trường hợp bạn cuộn nó xuống quá xa, có lẽ bạn đã không tìm thấy câu trả lời - đây là điều khác để kiểm tra.


12

Điều này có thể xảy ra khá dễ dàng nếu bạn quên liên kết đến tệp đối tượng có định nghĩa.


1
Vui lòng thêm một số mô tả để trả lời của bạn và có thể sửa chữa.
Mohit Jain

1
Quên liên kết có thể bao gồm việc quên thêm vào để xây dựng hướng dẫn. Trong trường hợp của tôi, tệp cpp của tôi có mọi thứ 'được xác định hoàn hảo' ngoại trừ tôi quên thêm tệp cpp vào danh sách nguồn (trong CMakeLists.txt của tôi, nhưng điều tương tự có thể xảy ra trong các hệ thống xây dựng khác như trong tệp .pro). Kết quả là, mọi thứ được biên dịch và sau đó tôi đã gặp lỗi tại thời điểm liên kết ...
sage

@Mohit Jain Cách liên kết trong các tệp đối tượng phụ thuộc vào thiết lập và công cụ môi trường. Thật không may, một bản sửa lỗi cụ thể cho một người có thể khác với một người khác (ví dụ: CMake vs công cụ độc quyền so với IDE so với v.v.)
Hazok

11

Trình biên dịch GNU C ++ phải đưa ra quyết định nơi đặt vtable trong trường hợp bạn có định nghĩa về các hàm ảo của một đối tượng trải rộng trên nhiều đơn vị biên dịch (ví dụ: một số đối tượng định nghĩa hàm ảo nằm trong tệp .cpp khác. tập tin cpp, và như vậy).

Trình biên dịch chọn đặt vtable cùng một vị trí với nơi xác định hàm ảo khai báo đầu tiên.

Bây giờ nếu bạn vì một lý do nào đó quên cung cấp định nghĩa cho hàm ảo đầu tiên được khai báo trong đối tượng (hoặc quên nhầm để thêm đối tượng được biên dịch ở giai đoạn liên kết), bạn sẽ gặp lỗi này.

Là một tác dụng phụ, xin lưu ý rằng chỉ đối với chức năng ảo cụ thể này, bạn sẽ không gặp phải lỗi liên kết truyền thống giống như bạn đang thiếu chức năng foo .


8

Không để bài chéo nhưng. Nếu bạn đang đối phó với thừa kế , lần truy cập thứ hai của google là điều tôi đã bỏ lỡ, tức là. tất cả các phương thức ảo nên được xác định.

Nhu la:

virtual void fooBar() = 0;

Xem Answare C ++ Tham chiếu không xác định đến vtable và kế thừa để biết chi tiết. Chỉ cần nhận ra rằng nó đã được đề cập ở trên, nhưng quái gì nó có thể giúp được ai đó.


8

Ok, giải pháp cho vấn đề này là bạn có thể đã bỏ lỡ định nghĩa. Xem ví dụ dưới đây để tránh lỗi trình biên dịch vtable:

// In the CGameModule.h

class CGameModule
{
public:
    CGameModule();
    ~CGameModule();

    virtual void init();
};

// In the CGameModule.cpp

#include "CGameModule.h"

CGameModule::CGameModule()
{

}

CGameModule::~CGameModule()
{

}

void CGameModule::init()    // Add the definition
{

}

7
  • Bạn có chắc chắn rằng CDasherComponentcó một cơ thể cho kẻ hủy diệt? Nó chắc chắn không có ở đây - câu hỏi là nếu nó nằm trong tệp .cc.
  • Từ quan điểm phong cách, CDasherModulenên xác định rõ ràng hàm hủy của nó virtual.
  • Có vẻ như CGameModulecó thêm một } phần cuối (sau}; // for the class ).
  • CGameModuleđược liên kết với các thư viện xác định CDasherModuleCDasherComponent?

- Có, CDasherComponent có phần thân hủy trong cpp. Tôi nghĩ rằng nó đã được tuyên bố trong .h khi tôi đăng bài này. - Ghi nhận hợp lệ. - Đó là một khung thêm tôi đã thêm do nhầm lẫn khi tước tài liệu. - Theo tôi hiểu thì có. Tôi đã sửa đổi một tệp tự động mà tôi không viết, nhưng tôi đã theo các mẫu đã làm việc cho các lớp khác có cùng kiểu kế thừa từ cùng một lớp, vì vậy trừ khi tôi mắc một lỗi ngu ngốc (Hoàn toàn có thể) , Tôi không nghĩ đó là nó.
RyanG

@RyanG: thử chuyển tất cả các định nghĩa hàm ảo sang định nghĩa lớp. Hãy chắc chắn rằng tất cả đều ở đó và xem nếu kết quả thay đổi.
Stephen

5

Có lẽ thiếu các yếu tố hủy diệt ảo là yếu tố góp phần?

virtual ~CDasherModule(){};

5

Đây là kết quả tìm kiếm đầu tiên đối với tôi vì vậy tôi nghĩ tôi nên thêm một điều nữa để kiểm tra: đảm bảo định nghĩa của các hàm ảo thực sự nằm trên lớp. Trong trường hợp của tôi, tôi đã có điều này:

Tập tin tiêu đề:

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

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

và trong tệp .cc của tôi:

void foo() {
  ...
}

Cái này nên đọc

void B::foo() {
}

3

Có lẽ không. Chắc chắn ~CDasherModule() {}là mất tích.


3

Rất nhiều câu trả lời ở đây nhưng dường như không có câu trả lời nào cho thấy vấn đề của tôi là gì. Tôi đã có những điều sau đây:


class I {
    virtual void Foo()=0;
};

Và trong một tập tin khác (tất nhiên bao gồm trong phần biên dịch và liên kết)

class C : public I{
    void Foo() {
        //bar
    }
};

Vâng, điều này đã không làm việc và tôi đã nhận được lỗi mà mọi người đang nói về. Để giải quyết nó, tôi đã phải chuyển định nghĩa thực tế của Foo ra khỏi khai báo lớp như sau:

class C : public I{
    void Foo();
};

C::Foo(){
   //bar
}

Tôi không phải là chuyên gia về C ++ nên tôi không thể giải thích tại sao điều này đúng hơn nhưng nó đã giải quyết được vấn đề cho tôi.


Tôi không phải là chuyên gia về C ++, nhưng dường như nó có liên quan đến việc trộn khai báo và định nghĩa trong cùng một tệp, với một tệp định nghĩa bổ sung.
Terry G Lorber

1
Khi định nghĩa hàm nằm trong định nghĩa lớp của bạn, nó được khai báo ngầm định là "nội tuyến". Tại thời điểm đó, tất cả các chức năng ảo của bạn đã được nội tuyến. Khi bạn di chuyển hàm bên ngoài định nghĩa lớp, nó không còn là hàm "nội tuyến". Vì bạn có một hàm ảo không nội tuyến, trình biên dịch của bạn biết nơi phát ra vtable. (Một lời giải thích kỹ lưỡng hơn sẽ không phù hợp với một bình luận.)
JaMiT

3

Vì vậy, tôi đã sử dụng Qt với trình biên dịch Windows XP và MinGW và điều này khiến tôi phát điên.

Về cơ bản moc_xxx.cpp được tạo trống ngay cả khi tôi được thêm vào

Q_OB DỰ ÁN

Xóa mọi thứ làm cho các chức năng trở nên ảo, rõ ràng và bất cứ điều gì bạn đoán không hoạt động. Cuối cùng tôi bắt đầu loại bỏ từng dòng một và hóa ra tôi đã có

#ifdef something

Xung quanh hồ sơ. Ngay cả khi #ifdef là tệp moc đúng không được tạo.

Vì vậy, việc xóa tất cả #ifdefs đã khắc phục sự cố.

Điều này đã không xảy ra với Windows và VS 2013.


Nhận xét dòng Q_OB DỰ Á đã khiến ứng dụng thử nghiệm đơn giản của tôi được xây dựng đơn giản g++ *.cpp .... (Cần một cái gì đó nhanh chóng và bẩn thỉu nhưng qmake đầy đau buồn.)
Nathan Kidd

2

Nếu vẫn thất bại, tìm kiếm sự trùng lặp. Tôi đã bị đánh giá sai bởi tham chiếu ban đầu rõ ràng đến các hàm tạo và hàm hủy cho đến khi tôi đọc một tham chiếu trong bài viết khác. Đó là bất kỳ phương pháp chưa được giải quyết. Trong trường hợp của tôi, tôi nghĩ rằng tôi đã thay thế khai báo sử dụng char * xml làm tham số bằng một tham số sử dụng const char * xml rắc rối không cần thiết, nhưng thay vào đó, tôi đã tạo một khai báo mới và đặt một vị trí khác.


2

Có rất nhiều khả năng được đề cập để gây ra lỗi này và tôi chắc chắn nhiều trong số chúng có thể gây ra lỗi. Trong trường hợp của tôi, đã có một định nghĩa của cùng một lớp, do sự trùng lặp của tệp nguồn. Tập tin này đã được biên dịch, nhưng không được liên kết, vì vậy trình liên kết đã phàn nàn về việc không thể tìm thấy nó.

Tóm lại, tôi sẽ nói rằng nếu bạn đã nhìn chằm chằm vào lớp đủ lâu và không thể thấy vấn đề cú pháp nào có thể gây ra, hãy tìm các vấn đề xây dựng như tệp bị thiếu hoặc tệp trùng lặp.


2

Trong trường hợp của tôi, tôi đang sử dụng Qt và đã xác định một QObjectlớp con trong tệp foo.cpp(không .h). Các sửa chữa là để thêm #include "foo.moc"vào cuối foo.cpp.


2

Tôi nghĩ điều đáng nói là bạn cũng sẽ nhận được thông báo khi bạn cố liên kết đến đối tượng của bất kỳ lớp nào có ít nhất một phương thức ảo và trình liên kết không thể tìm thấy tệp. Ví dụ:

Foo.hpp:

class Foo
{
public:
    virtual void StartFooing();
};

Foo.cpp:

#include "Foo.hpp"

void Foo::StartFooing(){ //fooing }

Tổng hợp với:

g++ Foo.cpp -c

Và chính.cpp:

#include "Foo.hpp"

int main()
{
    Foo foo;
}

Tổng hợp và liên kết với:

g++ main.cpp -o main

Cung cấp lỗi yêu thích của chúng tôi:

/tmp/cclKnW0g.o: Trong chức năng main': main.cpp:(.text+0x1a): undefined reference tovtable cho Foo 'coll2: error: ld trả về 1 trạng thái thoát

Điều này xảy ra từ becasue của tôi:

  1. Vtable được tạo cho mỗi lớp vào thời gian biên dịch

  2. Trình liên kết không có quyền truy cập vào vtable trong Foo.o


1

Tôi đã gặp lỗi này trong kịch bản sau đây

Hãy xem xét một trường hợp trong đó bạn đã xác định việc thực hiện các hàm thành viên của một lớp trong chính tệp tiêu đề. Tệp tiêu đề này là một tiêu đề được xuất (nói cách khác, nó có thể được sao chép sang một số phổ biến / bao gồm trực tiếp trong cơ sở mã của bạn). Bây giờ bạn đã quyết định tách việc thực hiện các hàm thành viên thành tệp .cpp. Sau khi bạn tách / chuyển việc triển khai sang .cpp, tệp tiêu đề giờ chỉ còn các nguyên mẫu của các hàm thành viên bên trong lớp. Sau những thay đổi ở trên, nếu bạn xây dựng cơ sở mã của mình, bạn có thể gặp lỗi "không xác định tham chiếu đến 'vtable ...".

Để khắc phục điều này, trước khi xây dựng, hãy đảm bảo bạn xóa tệp tiêu đề (mà bạn đã thực hiện thay đổi) trong thư mục chung / bao gồm. Đồng thời đảm bảo rằng bạn thay đổi tệp thực hiện của mình thành chỗ ở / thêm tệp .o mới được tạo từ tệp .cpp mới mà bạn vừa tạo. Khi bạn thực hiện các bước này, trình biên dịch / trình liên kết sẽ không còn phàn nàn nữa.


lạ. Nếu một tiêu đề sẽ được sao chép ở một nơi khác, hệ thống xây dựng sẽ tự động cập nhật bản sao ngay khi bản gốc được sửa đổi và trước khi đưa vào bất kỳ tệp nào khác. Nếu bạn phải làm nó bằng tay, bạn đang say sưa.
Offirmo

1

Tôi đã gặp loại lỗi này trong các tình huống khi tôi đang cố liên kết với một đối tượng khi tôi gặp một lỗi tạo ra ngăn không cho đối tượng được thêm vào kho lưu trữ.

Nói rằng tôi có libXYZ.a đáng lẽ phải có bioseq.o trong int nhưng không được.

Tôi đã gặp một lỗi:

combineseq.cpp:(.text+0xabc): undefined reference to `vtable for bioseq'

Điều này được bỏ khác nhau từ tất cả các bên trên. Tôi sẽ gọi đối tượng còn thiếu này trong vấn đề lưu trữ.


0

Cũng có thể bạn nhận được một tin nhắn như

SomeClassToTest.host.o: In function `class1::class1(std::string const&)':
class1.hpp:114: undefined reference to `vtable for class1'
SomeClassToTest.host.o: In function `class1::~class1()':
class1.hpp:119: undefined reference to `vtable for class1'
collect2: error: ld returned 1 exit status
[link] FAILED: 'g++' '-o' 'stage/tests/SomeClassToTest' 'object/tests/SomeClassToTest.host.o' 'object/tests/FakeClass1.SomeClassToTest.host.o'

nếu bạn quên định nghĩa một hàm ảo của lớp FakeClass1 khi bạn đang cố gắng liên kết một bài kiểm tra đơn vị cho một lớp khác.

//class declaration in class1.h
class class1
{
    public:
    class1()
    {
    }
    virtual ~class1()
    {
    }
    virtual void ForgottenFunc();
};

//class definition in FakeClass1.h
//...
//void ForgottenFunc() {} is missing here

Trong trường hợp này, tôi khuyên bạn nên kiểm tra giả mạo của bạn cho class1 một lần nữa. Có lẽ bạn sẽ thấy rằng bạn có thể đã quên định nghĩa một hàm ảo ForgottenFunctrong lớp giả mạo của mình.


0

Tôi đã gặp lỗi này khi tôi thêm một lớp thứ hai vào cặp nguồn / tiêu đề hiện có. Hai tiêu đề lớp trong cùng một tệp .h và định nghĩa hàm cho hai lớp trong cùng một tệp .cpp.

Tôi đã thực hiện điều này thành công trước đây, với các lớp có nghĩa là phối hợp chặt chẽ với nhau, nhưng dường như có gì đó không giống tôi lần này. Vẫn không biết gì, nhưng chia chúng thành một lớp cho mỗi đơn vị biên dịch đã sửa nó ngay.


Nỗ lực thất bại:

_gui_icondata.h:

#ifndef ICONDATA_H
#define ICONDATA_H

class Data;
class QPixmap;

class IconData
{
public:
    explicit IconData();
    virtual ~IconData();

    virtual void setData(Data* newData);
    Data* getData() const;
    virtual const QPixmap* getPixmap() const = 0;

    void toggleSelected();
    void toggleMirror();
    virtual void updateSelection() = 0;
    virtual void updatePixmap(const QPixmap* pixmap) = 0;

protected:
    Data* myData;
};

//--------------------------------------------------------------------------------------------------

#include "_gui_icon.h"

class IconWithData : public Icon, public IconData
{
    Q_OBJECT
public:
    explicit IconWithData(QWidget* parent);
    virtual ~IconWithData();

    virtual const QPixmap* getPixmap() const;
    virtual void updateSelection();
    virtual void updatePixmap(const QPixmap* pixmap);

signals:

public slots:
};

#endif // ICONDATA_H

_gui_icondata.cpp:

#include "_gui_icondata.h"

#include "data.h"

IconData::IconData()
{
    myData = 0;
}

IconData::~IconData()
{
    if(myData)
    {
        myData->removeIcon(this);
    }
    //don't need to clean up any more; this entire object is going away anyway
}

void IconData::setData(Data* newData)
{
    if(myData)
    {
        myData->removeIcon(this);
    }
    myData = newData;
    if(myData)
    {
        myData->addIcon(this, false);
    }
    updateSelection();
}

Data* IconData::getData() const
{
    return myData;
}

void IconData::toggleSelected()
{
    if(!myData)
    {
        return;
    }

    myData->setSelected(!myData->getSelected());
    updateSelection();
}

void IconData::toggleMirror()
{
    if(!myData)
    {
        return;
    }

    myData->setMirrored(!myData->getMirrored());
    updateSelection();
}

//--------------------------------------------------------------------------------------------------

IconWithData::IconWithData(QWidget* parent) :
    Icon(parent), IconData()
{
}

IconWithData::~IconWithData()
{
}

const QPixmap* IconWithData::getPixmap() const
{
    return Icon::pixmap();
}

void IconWithData::updateSelection()
{
}

void IconWithData::updatePixmap(const QPixmap* pixmap)
{
    Icon::setPixmap(pixmap, true, true);
}

Một lần nữa, thêm một cặp nguồn / tiêu đề mới và cắt / dán nguyên văn lớp IconWithData vào đó "vừa hoạt động".


0

Trường hợp của tôi là một trường hợp ngớ ngẩn, tôi đã có thêm "sau khi #includenhầm lẫn và đoán những gì?

undefined reference to vtable!

Tôi đã gãi đầu và mặt tái mét hàng giờ để bình luận các chức năng ảo để xem có gì thay đổi không, và cuối cùng bằng cách loại bỏ phần phụ " , mọi thứ đã được sửa! Những loại điều thực sự cần phải dẫn đến lỗi biên dịch không phải lỗi liên kết.

Ngoài ra ", ý tôi là:

#include "SomeHeader.h""

0

Trong trường hợp của tôi, tôi có một lớp cơ sở tên là Person và hai lớp xuất phát tên là Sinh viên và Giáo sư.

Chương trình của tôi đã được sửa như thế nào, 1. Tôi đã thực hiện tất cả các chức năng trong lớp cơ sở Pure Virtual. 2. Tôi đã sử dụng tất cả các hàm hủy ảo nhưdefault ones.


-3

Tôi đã gặp lỗi này chỉ vì tên của một đối số hàm tạo khác nhau trong tệp tiêu đề và trong tệp thực hiện. Chữ ký của nhà xây dựng là

PointSet (const PointSet & pset, Parent * parent = 0);

và những gì tôi đã viết trong quá trình thực hiện bắt đầu với

PointSet (const PointSet & pest, Parent * parent)

do đó tôi vô tình thay thế "pset" bằng "dịch hại". Trình biên dịch đã phàn nàn về điều này và hai nhà xây dựng khác trong đó không có lỗi nào cả. Tôi đang sử dụng g ++ phiên bản 4.9.1 trong Ubuntu. Và việc định nghĩa một hàm hủy ảo trong lớp dẫn xuất này không tạo ra sự khác biệt (nó được định nghĩa trong lớp cơ sở). Tôi sẽ không bao giờ tìm thấy lỗi này nếu tôi không dán phần thân của các nhà xây dựng vào tệp tiêu đề, do đó xác định chúng trong lớp.


7
Điều đó sẽ không có gì khác biệt cả, bạn chắc chắn đã có lỗi ở nơi khác và vô tình sửa nó.
MM
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.