2008-10-17 44 views
25

Tôi có tệp tiêu đề C++ chứa một lớp. Tôi muốn sử dụng lớp này trong một số dự án, bu tôi không muốn tạo ra một thư viện riêng cho nó, vì vậy tôi đặt cả hai phương pháp khai báo và định nghĩa trong file header:lỗi định nghĩa nhiều bao gồm tệp tiêu đề C++ có mã nội tuyến từ nhiều nguồn

// example.h 
#ifndef EXAMPLE_H_ 
#define EXAMPLE_H_ 
namespace test_ns{ 

class TestClass{ 
public: 
    void testMethod(); 
}; 

void TestClass::testMethod(){ 
    // some code here... 
} 

} // end namespace test_ns 
#endif 

Nếu bên trong cùng dự án tôi bao gồm tiêu đề này từ nhiều hơn một tập tin cpp, tôi nhận được một lỗi nói rằng "multiple definition of test_ns::TestClass::testMethod()", trong khi nếu tôi đặt định nghĩa phương pháp bên trong cơ thể lớp này không xảy ra:

// example.h 
#ifndef EXAMPLE_H_ 
#define EXAMPLE_H_ 
namespace test_ns{ 

class TestClass{ 
public: 
    void testMethod(){ 
     // some code here... 
    } 
}; 

} // end namespace test_ns 
#endif 

Kể từ khi lớp được định nghĩa bên trong một không gian tên, không nên hai hình thức tương đương? Tại sao phương pháp được coi là được xác định hai lần trong trường hợp đầu tiên?

Trả lời

20

Đây không phải là tương đương. Ví dụ thứ hai được đưa ra có một sửa đổi 'inline' ngầm định trên phương thức và do đó trình biên dịch sẽ hòa giải nhiều định nghĩa (có khả năng nhất với liên kết nội bộ của phương thức nếu nó không có khả năng inlineable).

Ví dụ đầu tiên không phải là nội tuyến và vì vậy nếu tiêu đề này được bao gồm trong nhiều đơn vị dịch thì bạn sẽ có nhiều định nghĩa và lỗi trình liên kết.

Ngoài ra, tiêu đề phải luôn được bảo vệ để ngăn chặn nhiều lỗi định nghĩa trong cùng một đơn vị dịch. Điều đó sẽ chuyển đổi tiêu đề của bạn để:

#ifndef EXAMPLE_H 
#define EXAMPLE_H 

//define your class here 

#endif 
+0

Cảm ơn bạn đã chỉ ra điều này ... Tôi đã quên các trình bao gồm trong ví dụ (nhưng không phải trong mã thực tế). –

20

Bên trong thân lớp được coi là nội tuyến bởi trình biên dịch. Nếu bạn thực hiện bên ngoài cơ thể, nhưng vẫn còn trong tiêu đề, bạn phải đánh dấu phương thức là 'nội tuyến' một cách rõ ràng.

namespace test_ns{ 

class TestClass{ 
public: 
    inline void testMethod(); 
}; 

void TestClass::testMethod(){ 
    // some code here... 
} 

} // end namespace test_ns 

Sửa

Đối với bản thân mình nó thường giúp để giải quyết những loại biên dịch các vấn đề bằng cách nhận ra rằng trình biên dịch không nhìn thấy bất cứ điều gì giống như một tập tin tiêu đề. Các tệp tiêu đề được xử lý trước và trình biên dịch chỉ thấy một tệp lớn chứa mọi dòng từ mọi tệp được bao gồm (đệ quy). Thông thường điểm bắt đầu cho các đệ quy này bao gồm một tệp nguồn cpp đang được biên dịch. Trong công ty của chúng tôi, ngay cả một tệp cpp có kích thước khiêm tốn cũng có thể được trình bày với trình biên dịch dưới dạng quái vật dòng 300000. Vì vậy, khi một phương thức, mà không được khai báo nội tuyến, được thực hiện trong một tệp tiêu đề, trình biên dịch có thể kết thúc thấy void TestClass :: testMethod() {...} hàng chục lần trong tệp được xử lý trước. Bây giờ bạn có thể thấy rằng điều này không có ý nghĩa, cùng một hiệu ứng như bạn nhận được khi sao chép/dán nó nhiều lần trong một tệp nguồn. Và ngay cả khi bạn đã thành công bằng cách chỉ có nó một lần trong mọi đơn vị biên dịch, bằng một số hình thức biên dịch có điều kiện (ví dụ: sử dụng dấu ngoặc bao gồm), trình liên kết sẽ vẫn tìm thấy biểu tượng của phương thức này trong nhiều đơn vị được biên dịch (tệp đối tượng).

3

Đừng đặt một định nghĩa hàm/phương pháp trong một tập tin tiêu đề, trừ khi họ đều được sắp xếp (bằng cách định nghĩa chúng trực tiếp trong một tuyên bố lớp hoặc explicity xác định bởi các từ khóa inline)

tệp tiêu đề là (chủ yếu) để khai báo (bất kỳ điều gì bạn cần khai báo). Các định nghĩa được cho phép là các hằng số cho các hằng số và các hàm/phương thức nội tuyến (và các khuôn mẫu).

1

Đoạn mã đầu tiên của bạn bị lỗi "Quy tắc một định nghĩa" của C++ - see here for a link to a Wikipedia article describing ODR. Bạn đang thực sự rơi vào điểm # 2 vì mỗi lần trình biên dịch bao gồm tệp tiêu đề vào tệp nguồn, bạn gặp rủi ro trình biên dịch tạo ra một định nghĩa toàn cầu có thể nhìn thấy của test_ns::TestClass::testMethod(). Và tất nhiên vào lúc bạn liên kết mã, trình liên kết sẽ có chú mèo con vì nó sẽ tìm thấy cùng biểu tượng trong nhiều tệp đối tượng.

Đoạn thứ hai hoạt động vì bạn đã định nghĩa hàm, có nghĩa là ngay cả khi trình biên dịch không tạo bất kỳ mã nội tuyến nào cho hàm (nói, bạn đã tắt nội tuyến hoặc trình biên dịch quyết định hàm quá lớn để nội tuyến), mã được tạo cho định nghĩa hàm sẽ chỉ hiển thị trong đơn vị dịch, như thể bạn đã đặt nó trong một không gian tên ẩn danh. Do đó bạn nhận được nhiều bản sao của hàm trong mã đối tượng được tạo mà trình liên kết có thể hoặc không thể tối ưu hóa tùy thuộc vào mức độ thông minh của nó.

Bạn có thể đạt được hiệu ứng tương tự trong đoạn mã đầu tiên của mình bằng cách đặt trước TestClass::testMethod() với inline.

-1
//Baseclass.h or .cpp 

#ifndef CDerivedclass 
#include "Derivedclass.h" 
#endif 

or 
//COthercls.h or .cpp 

#ifndef CCommonheadercls 
#include "Commonheadercls.h" 
#endif 

I think this suffice all instances. 
2

Thực tế có thể có định nghĩa trong một tệp tiêu đề duy nhất (không có tệp .c/.cpp riêng biệt) và vẫn có thể sử dụng nó từ nhiều tệp nguồn.

Cân nhắc foobar.h tiêu đề này:

#ifndef FOOBAR_H 
#define FOOBAR_H 

/* write declarations normally */ 
void foo(); 
void bar(); 

/* use conditional compilation to disable definitions when necessary */ 
#ifndef ONLY_DECLARATIONS 
void foo() { 
    /* your code goes here */ 
} 
void bar() { 
    /* your code goes here */ 
} 
#endif /* ONLY_DECLARATIONS */ 
#endif /* FOOBAR_H */ 

Nếu bạn sử dụng tiêu đề này chỉ trong một tập tin nguồn, bao gồm và sử dụng nó bình thường. Giống như trong main.c:

#include "foobar.h" 

int main(int argc, char *argv[]) { 
    foo(); 
} 

Nếu có là tập tin nguồn khác trong dự án của bạn mà yêu cầu foobar.h, sau đó #define ONLY_DECLARATIONS vĩ mô trước khi đưa nó. Trong use_bar.c bạn có thể viết:

#define ONLY_DECLARATIONS 
#include "foobar.h" 

void use_bar() { 
    bar(); 
} 

Sau use_bar.o biên soạn và main.o có thể được liên kết với nhau mà không có lỗi, bởi vì chỉ có một trong số họ (main.o) sẽ phải thi hành foo() và thanh ().

Đó là hơi không thành ngữ, nhưng nó cho phép để giữ cho các định nghĩa và khai báo với nhau trong một tập tin. Tôi cảm thấy như là một người đàn ông nghèo thay thế cho đúng modules.

Các vấn đề liên quan