Tại sao C ++ lại có các hàm nội tuyến trong tiêu đề?


120

NB Đây không phải là câu hỏi về cách sử dụng các hàm nội tuyến hoặc cách chúng hoạt động, mà là lý do tại sao chúng được thực hiện theo cách của chúng.

Việc khai báo một hàm thành viên lớp không cần phải định nghĩa một hàm như thế nào inline, nó chỉ là việc triển khai thực tế của hàm. Ví dụ, trong tệp tiêu đề:

struct foo{
    void bar(); // no need to define this as inline
}

Vậy tại sao việc triển khai nội tuyến của một hàm lớp lại phải nằm trong tệp tiêu đề? Tại sao tôi không thể đặt hàm nội tuyến vào .cpptệp? Nếu tôi cố gắng đưa định nghĩa nội tuyến vào .cpptệp, tôi sẽ gặp lỗi dọc theo các dòng:

error LNK2019: unresolved external symbol 
"public: void __thiscall foo::bar(void)"
(?bar@foo@@QAEXXZ) referenced in function _main 
1>C:\Users\Me\Documents\Visual Studio 2012\Projects\inline\Debug\inline.exe 
: fatal error LNK1120: 1 unresolved externals



@Charles Tôi sẽ nói rằng liên kết thứ hai của tôi tương tự, nhưng tôi đang hỏi thêm về logic đằng sau lý do tại sao nội tuyến hoạt động theo cách nó làm.
thecoshman

2
Trong trường hợp đó, tôi nghĩ bạn có thể đã hiểu nhầm "nội tuyến" hoặc "tệp tiêu đề"; cả hai khẳng định của bạn đều không đúng. Bạn có thể có một triển khai nội tuyến của một hàm thành viên và bạn có thể đặt các định nghĩa hàm nội tuyến trong tệp tiêu đề, nó chỉ có thể không phải là một ý tưởng hay. Bạn có thể làm rõ câu hỏi của bạn?
CB Bailey

Đăng chỉnh sửa, tôi nghĩ bạn có thể hỏi về các tình huống khi inlinexuất hiện trên một định nghĩa nhưng không phải là một khai báo trước và ngược lại . Nếu vậy, điều này có thể hữu ích: stackoverflow.com/questions/4924912/…
CB Bailey

Câu trả lời:


122

Định nghĩa của một inlinehàm không nhất thiết phải có trong tệp tiêu đề nhưng do một quy tắc định nghĩa ( ODR ) cho các hàm nội tuyến, một định nghĩa giống hệt cho hàm phải tồn tại trong mọi đơn vị dịch sử dụng nó.

Cách dễ nhất để đạt được điều này là đưa định nghĩa vào tệp tiêu đề.

Nếu bạn muốn đặt định nghĩa của một hàm trong một tệp nguồn duy nhất thì bạn không nên khai báo nó inline. Một hàm không được khai báo inlinekhông có nghĩa là trình biên dịch không thể nội dòng hàm.

Việc bạn có nên khai báo một hàm inlinehay không thường là lựa chọn mà bạn nên thực hiện dựa trên phiên bản nào của một quy tắc định nghĩa mà nó có ý nghĩa nhất để bạn tuân theo; thêm inlinevà sau đó bị hạn chế bởi các ràng buộc tiếp theo không có ý nghĩa gì.


Nhưng không phải trình biên dịch sẽ biên dịch tệp .cpp, trong đó bao gồm các tệp .h ... để khi biên dịch tệp .cpp, nó có cả giảm tốc cũng như các tệp nguồn. Tập tin tiêu đề khác kéo vào chỉ của họ để trình biên dịch có thể 'niềm tin' rằng những chức năng vẫn tồn tại và sẽ được thực hiện trong một số tập tin nguồn khác
thecoshman

1
Đây thực sự là một câu trả lời tốt hơn của tôi, +1từ tôi!
sbi

2
@thecoshman: Có hai điểm phân biệt. Tệp nguồn so với tệp tiêu đề. Theo quy ước, tệp tiêu đề thường đề cập đến tệp nguồn không phải là cơ sở cho đơn vị dịch mà chỉ #included từ các tệp nguồn khác. Sau đó là khai báo vs định nghĩa. Bạn có thể có các khai báo hoặc định nghĩa của các hàm trong tệp tiêu đề hoặc tệp nguồn 'bình thường'. Tôi e rằng tôi không chắc bạn đang hỏi gì trong nhận xét của mình.
CB Bailey

đừng lo lắng, tôi hiểu tại sao bây giờ lại như vậy ... mặc dù tôi không chắc ai thực sự trả lời câu hỏi này. Sự kết hợp giữa câu trả lời của bạn và @ Xanatos đã giải thích cho tôi.
thecoshman

113

Có hai cách để xem xét nó:

  1. Các hàm nội tuyến được định nghĩa trong tiêu đề bởi vì, để nội dòng một lệnh gọi hàm, trình biên dịch phải có thể nhìn thấy thân hàm. Đối với một trình biên dịch ngây thơ để làm điều đó, thân hàm phải ở cùng một đơn vị dịch với lệnh gọi. (Một trình biên dịch hiện đại có thể tối ưu hóa giữa các đơn vị dịch và do đó, một lệnh gọi hàm có thể được đặt trong mặc dù định nghĩa hàm nằm trong một đơn vị dịch riêng biệt, nhưng những tối ưu hóa này rất tốn kém, không phải lúc nào cũng được kích hoạt và không phải lúc nào cũng được hỗ trợ bởi trình biên dịch)

  2. các hàm được xác định trong tiêu đề phải được đánh dấu inlinevì nếu không, mọi đơn vị dịch bao gồm tiêu đề sẽ chứa định nghĩa của hàm và trình liên kết sẽ phàn nàn về nhiều định nghĩa (vi phạm Quy tắc Một Định nghĩa). Các inlinetừ khóa ngăn chặn này, cho phép nhiều đơn vị dịch thuật để chứa các định nghĩa (giống hệt nhau).

Hai giải thích thực sự dẫn đến thực tế là inlinetừ khóa không thực hiện chính xác những gì bạn mong đợi.

Trình biên dịch C ++ có thể tự do áp dụng tối ưu hóa nội tuyến (thay thế một lệnh gọi hàm bằng phần thân của hàm được gọi, lưu chi phí cuộc gọi) bất cứ lúc nào nó thích, miễn là nó không làm thay đổi hành vi quan sát được của chương trình.

Các inlinetừ khóa làm cho nó dễ dàng hơn cho các trình biên dịch để áp dụng tối ưu hóa này, bằng cách cho phép định nghĩa chức năng để được nhìn thấy trong nhiều đơn vị dịch thuật, nhưng sử dụng các từ khóa không có nghĩa là trình biên dịch đã để nội tuyến chức năng, và không sử dụng từ khóa không cấm trình biên dịch nội tuyến hàm.


23

Đây là một giới hạn của trình biên dịch C ++. Nếu bạn đặt hàm trong tiêu đề, tất cả các tệp cpp nơi nó có thể được nội dòng có thể nhìn thấy "nguồn" của hàm của bạn và việc nội tuyến có thể được thực hiện bởi trình biên dịch. Mặt khác, nội tuyến sẽ phải được thực hiện bởi trình liên kết (mỗi tệp cpp được biên dịch trong một tệp obj riêng biệt). Vấn đề là nó sẽ khó hơn nhiều để làm điều đó trong trình liên kết. Một vấn đề tương tự cũng tồn tại với các lớp / hàm "mẫu". Chúng cần được trình biên dịch khởi tạo, vì trình liên kết sẽ gặp vấn đề khi khởi tạo (tạo phiên bản chuyên biệt của) chúng. Một số trình biên dịch / trình liên kết mới hơn có thể thực hiện biên dịch / liên kết "hai lần" trong đó trình biên dịch thực hiện lần chuyển đầu tiên, sau đó trình liên kết thực hiện công việc của nó và gọi trình biên dịch để giải quyết những thứ chưa được giải quyết (nội tuyến / mẫu ...)


Ồ, tôi hiểu rồi! vâng, nó không dành cho lớp mà nó tự sử dụng hàm nội tuyến, mã khác của nó sử dụng các hàm nội tuyến. Họ chỉ nhìn thấy tệp tiêu đề cho lớp đang được nội tuyến!
thecoshman

11
Tôi không đồng ý với câu trả lời này, nó không phải là giới hạn của trình biên dịch C ++; nó hoàn toàn là cách các quy tắc ngôn ngữ được chỉ định. Các quy tắc ngôn ngữ cho phép một mô hình biên dịch đơn giản nhưng chúng không cấm các triển khai thay thế.
CB Bailey

3
Tôi đồng ý với @Charles. Trên thực tế, có những trình biên dịch có chức năng nội tuyến trên các đơn vị dịch, vì vậy điều này chắc chắn không phải do giới hạn của trình biên dịch.
sbi

5
Mặc dù câu trả lời này dường như có một số lỗi kỹ thuật, nhưng nó đã giúp tôi xem cách trình biên dịch hoạt động với các tệp tiêu đề và những thứ như vậy.
thecoshman

10

Lý do là trình biên dịch phải thực sự xem định nghĩa để có thể đưa nó vào vị trí của cuộc gọi.

Hãy nhớ rằng C và C ++ sử dụng một mô hình biên dịch rất đơn giản, trong đó trình biên dịch luôn chỉ thấy một đơn vị dịch tại một thời điểm. (Điều này không thành công khi xuất, đó là lý do chính chỉ có một nhà cung cấp thực sự triển khai nó.)


9

inlineTừ khóa c ++ gây hiểu lầm, nó không có nghĩa là "nội dòng hàm này". Nếu một hàm được định nghĩa là nội tuyến, điều đó đơn giản có nghĩa là nó có thể được định nghĩa nhiều lần miễn là tất cả các định nghĩa đều bằng nhau. Việc một hàm được đánh dấu inlinelà một hàm thực được gọi là hoàn toàn hợp pháp thay vì nhận được mã nội tuyến tại điểm mà nó được gọi.

Việc xác định một hàm trong tệp tiêu đề là cần thiết cho các mẫu, vì ví dụ: một lớp mẫu không thực sự là một lớp, nó là một mẫu cho một lớp mà bạn có thể tạo nhiều biến thể. Để trình biên dịch có thể tạo một Foo<int>::bar()hàm khi bạn sử dụng mẫu Foo để tạo lớp Foo , định nghĩa thực tế của Foo<T>::bar()phải hiển thị.


Và vì nó là một khuôn mẫu cho một lớp , nó không được gọi là lớp mẫu mà là một mẫu lớp .
sbi

4
Đoạn đầu tiên là hoàn toàn đúng (và tôi ước tôi có thể nhấn mạnh "gây hiểu lầm"), nhưng tôi không thấy cần thiết phải có phần không tuần tự vào các mẫu.
Thomas Edleson

Một số trình biên dịch sẽ sử dụng nó như một gợi ý rằng hàm có thể có thể được nội tuyến, nhưng thực sự, nó không được đảm bảo sẽ được nội tuyến chỉ vì bạn khai báo nó inline(cũng như không khai báo nó inlineđảm bảo rằng nó sẽ không được nội dòng).
Keith M

4

Tôi biết đây là một chủ đề cũ nhưng tôi nghĩ tôi nên đề cập đến externtừ khóa đó. Gần đây tôi đã gặp phải vấn đề này và giải quyết như sau

Helper.h

namespace DX
{
    extern inline void ThrowIfFailed(HRESULT hr);
}

Helper.cpp

namespace DX
{
    inline void ThrowIfFailed(HRESULT hr)
    {
        if (FAILED(hr))
        {
            std::stringstream ss;
            ss << "#" << hr;
            throw std::exception(ss.str().c_str());
        }
    }
}

6
Điều này thường sẽ không dẫn đến việc hàm thực sự được nội tuyến trừ khi bạn đang sử dụng Tối ưu hóa toàn bộ chương trình (WPO).
Chuck Walbourn

3

Bởi vì trình biên dịch cần nhìn thấy chúng để nội dòng chúng. Và các tệp tiêu đề là "thành phần" thường được bao gồm trong các đơn vị dịch khác.

#include "file.h"
// Ok, now me (the compiler) can see the definition of that inline function. 
// So I'm able to replace calls for the actual implementation.

1

Chức năng nội tuyến

Trong C ++, macro không là gì ngoài hàm nội tuyến. SO bây giờ macro nằm dưới sự kiểm soát của trình biên dịch.

  • Quan trọng : Nếu chúng ta xác định một hàm bên trong lớp, nó sẽ tự động trở thành Nội tuyến

Mã của hàm Inline được thay thế tại nơi nó được gọi, vì vậy nó làm giảm chi phí của hàm gọi.

Trong một số trường hợp Nội tuyến của hàm không thể hoạt động, chẳng hạn như

  • Nếu biến static được sử dụng bên trong hàm nội tuyến.

  • Nếu hàm phức tạp.

  • Nếu gọi đệ quy của hàm

  • Nếu địa chỉ của chức năng được thực hiện một cách ẩn ý hoặc rõ ràng

Hàm được định nghĩa bên ngoài lớp như bên dưới có thể trở thành nội tuyến

inline int AddTwoVar(int x,int y); //This may not become inline 

inline int AddTwoVar(int x,int y) { return x + y; } // This becomes inline

Hàm được định nghĩa bên trong lớp cũng trở thành nội tuyến

// Inline SpeedMeter functions
class SpeedMeter
{
    int speed;
    public:
    int getSpeed() const { return speed; }
    void setSpeed(int varSpeed) { speed = varSpeed; }
};
int main()
{
    SpeedMeter objSM;
    objSM.setSpeed(80);
    int speedValue = A.getSpeed();
} 

Ở đây cả hai hàm getSpeed ​​và setSpeed ​​sẽ trở thành nội tuyến


ơ, có lẽ một số thông tin hay, nhưng không thực sự cố gắng giải thích tại sao . Có thể bạn làm, nhưng chỉ là bạn không nói rõ.
thecoshman

2
Câu lệnh sau không đúng: "Quan trọng: Nếu chúng ta xác định một hàm bên trong lớp, nó sẽ tự động trở thành Nội tuyến" Ngay cả khi bạn viết "nội tuyến" trong khai báo / định nghĩa, bạn có thể chắc chắn rằng nó đang được nội tuyến. Thậm chí không phải đối với các mẫu. Có thể ý của bạn là trình biên dịch tự động giả định từ khóa "inline", nhưng không cần phải tuân theo và những gì tôi nhận thấy là trong hầu hết các trường hợp, nó không nội dòng các định nghĩa trong tiêu đề như vậy, ngay cả đối với các hàm constexpr đơn giản với số học cơ bản.
Pablo Ariel

Xin cảm ơn vì đã nhận xét ... Dưới đây là những dòng từ Tư duy trong C ++ micc.unifi.it/bertini/download/programmazione/… Trang 400 .. Vui lòng kiểm tra .. Vui lòng ủng hộ nếu bạn đồng ý. Cảm ơn ..... Nội tuyến bên trong các lớp Để định nghĩa một hàm nội tuyến, thông thường bạn phải đặt trước định nghĩa hàm bằng từ khóa nội tuyến. Tuy nhiên, điều này là không cần thiết bên trong định nghĩa lớp. Bất kỳ hàm nào bạn xác định bên trong định nghĩa lớp sẽ tự động là một nội tuyến.
Saurabh Raoot 27/09/18

Các tác giả của cuốn sách đó có thể yêu cầu những gì họ muốn, bởi vì họ viết sách chứ không phải viết mã. Đây là điều mà tôi đã phải phân tích sâu để làm cho các bản demo 3d di động của tôi có dung lượng dưới 64kb bằng cách tránh mã nội tuyến càng nhiều càng tốt. Lập trình là về sự thật chứ không phải tôn giáo, vì vậy sẽ không thực sự quan trọng nếu một số "lập trình viên thần thánh" nói điều đó trong một cuốn sách nếu nó không đại diện cho những gì xảy ra trong thực tế. Và hầu hết các sách C ++ ngoài kia đều có một bộ sưu tập những lời khuyên tồi, trong đó thỉnh thoảng bạn có thể tìm ra một số mẹo nhỏ để thêm vào kho của mình.
Pablo Ariel

Xin chào @PabloAriel Cảm ơn ... Vui lòng phân tích và cho tôi biết .. Tôi đồng ý cập nhật câu trả lời này theo phân tích
Saurabh Raoot, 27/09/18
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.