Giải quyết các lỗi xây dựng do phụ thuộc vòng tròn giữa các lớp


353

Tôi thường thấy mình trong tình huống phải đối mặt với nhiều lỗi biên dịch / liên kết trong dự án C ++ do một số quyết định thiết kế xấu (do người khác thực hiện :)) dẫn đến sự phụ thuộc vòng tròn giữa các lớp C ++ trong các tệp tiêu đề khác nhau (cũng có thể xảy ra trong cùng một tập tin) . Nhưng may mắn thay (?) Điều này không xảy ra thường xuyên đủ để tôi nhớ giải pháp cho vấn đề này cho lần tiếp theo nó lại xảy ra.

Vì vậy, với mục đích dễ dàng nhớ lại trong tương lai, tôi sẽ đăng một vấn đề đại diện và giải pháp cùng với nó. Các giải pháp tốt hơn tất nhiên được chào đón.


  • A.h

    class B;
    class A
    {
        int _val;
        B *_b;
    public:
    
        A(int val)
            :_val(val)
        {
        }
    
        void SetB(B *b)
        {
            _b = b;
            _b->Print(); // COMPILER ERROR: C2027: use of undefined type 'B'
        }
    
        void Print()
        {
            cout<<"Type:A val="<<_val<<endl;
        }
    };
    

  • B.h

    #include "A.h"
    class B
    {
        double _val;
        A* _a;
    public:
    
        B(double val)
            :_val(val)
        {
        }
    
        void SetA(A *a)
        {
            _a = a;
            _a->Print();
        }
    
        void Print()
        {
            cout<<"Type:B val="<<_val<<endl;
        }
    };
    

  • main.cpp

    #include "B.h"
    #include <iostream>
    
    int main(int argc, char* argv[])
    {
        A a(10);
        B b(3.14);
        a.Print();
        a.SetB(&b);
        b.Print();
        b.SetA(&a);
        return 0;
    }
    

23
Khi làm việc với Visual Studio, cờ / showIncludes giúp khắc phục rất nhiều vấn đề này.
lau

Câu trả lời:


288

Cách nghĩ về điều này là "nghĩ như một trình biên dịch".

Hãy tưởng tượng bạn đang viết một trình biên dịch. Và bạn thấy mã như thế này.

// file: A.h
class A {
  B _b;
};

// file: B.h
class B {
  A _a;
};

// file main.cc
#include "A.h"
#include "B.h"
int main(...) {
  A a;
}

Khi bạn đang biên dịch tệp .cc (hãy nhớ rằng .cc chứ không phải .h là đơn vị biên dịch), bạn cần phân bổ không gian cho đối tượng A. Vì vậy, tốt, bao nhiêu không gian sau đó? Đủ để lưu trữ B! Kích thước của Bnó là bao nhiêu? Đủ để lưu trữ A! Giáo sư.

Rõ ràng một tài liệu tham khảo tròn mà bạn phải phá vỡ.

Bạn có thể phá vỡ nó bằng cách cho phép trình biên dịch thay thế dự trữ nhiều không gian như nó biết về trả trước - ví dụ, con trỏ và tham chiếu, sẽ luôn là 32 hoặc 64 bit (tùy thuộc vào kiến ​​trúc) và vì vậy nếu bạn thay thế (một trong hai) một con trỏ hoặc tài liệu tham khảo, mọi thứ sẽ rất tuyệt Hãy nói rằng chúng tôi thay thế trong A:

// file: A.h
class A {
  // both these are fine, so are various const versions of the same.
  B& _b_ref;
  B* _b_ptr;
};

Bây giờ mọi thứ đã tốt hơn. Một chút nào đó. main()vẫn nói:

// file: main.cc
#include "A.h"  // <-- Houston, we have a problem

#include, cho tất cả các phạm vi và mục đích (nếu bạn lấy bộ tiền xử lý ra) chỉ cần sao chép tệp vào .cc . Vì vậy, thực sự, .cc trông giống như:

// file: partially_pre_processed_main.cc
class A {
  B& _b_ref;
  B* _b_ptr;
};
#include "B.h"
int main (...) {
  A a;
}

Bạn có thể thấy lý do tại sao trình biên dịch không thể giải quyết vấn đề này - nó không biết nó Blà gì - nó thậm chí chưa bao giờ nhìn thấy biểu tượng trước đó.

Vì vậy, hãy nói với trình biên dịch về B. Điều này được gọi là một tuyên bố chuyển tiếp , và được thảo luận thêm trong câu trả lời này .

// main.cc
class B;
#include "A.h"
#include "B.h"
int main (...) {
  A a;
}

Điều này hoạt động . Nó không tuyệt lắm . Nhưng tại thời điểm này, bạn nên hiểu rõ về vấn đề tham chiếu vòng tròn và những gì chúng tôi đã làm để "khắc phục" nó, mặc dù cách khắc phục là không tốt.

Lý do sửa lỗi này là xấu bởi vì người tiếp theo #include "A.h"sẽ phải khai báo Btrước khi họ có thể sử dụng nó và sẽ nhận được một #includelỗi khủng khiếp . Vì vậy, hãy chuyển bản khai vào chính Ah .

// file: A.h
class B;
class A {
  B* _b; // or any of the other variants.
};

Và trong Bh , tại thời điểm này, bạn có thể chỉ cần #include "A.h"trực tiếp.

// file: B.h
#include "A.h"
class B {
  // note that this is cool because the compiler knows by this time
  // how much space A will need.
  A _a; 
}

HTH.


20
"Nói với trình biên dịch về B" được biết đến như một tuyên bố chuyển tiếp của B.
Peter Ajtai

8
Chúa ơi! hoàn toàn bỏ lỡ thực tế là các tài liệu tham khảo được biết về mặt không gian bị chiếm đóng. Cuối cùng, bây giờ tôi có thể thiết kế đúng!
kelloss

47
Nhưng vẫn không thể sử dụng bất kỳ chức năng nào trên B (như trong câu hỏi _b-> Printt ())
xếp hạng1

3
Đây là vấn đề tôi đang gặp phải. Làm thế nào để bạn đưa các hàm vào với khai báo chuyển tiếp mà không viết lại hoàn toàn tệp tiêu đề?
sydan


101

Bạn có thể tránh các lỗi biên dịch nếu bạn loại bỏ các định nghĩa phương thức khỏi các tệp tiêu đề và để các lớp chỉ chứa các khai báo phương thức và các khai báo / định nghĩa biến. Các định nghĩa phương thức nên được đặt trong tệp .cpp (giống như hướng dẫn thực hành tốt nhất nói).

Mặt trái của giải pháp sau là (giả sử rằng bạn đã đặt các phương thức trong tệp tiêu đề để nội tuyến chúng) rằng các phương thức không còn được trình biên dịch nội tuyến và cố gắng sử dụng từ khóa nội tuyến tạo ra lỗi liên kết.

//A.h
#ifndef A_H
#define A_H
class B;
class A
{
    int _val;
    B* _b;
public:

    A(int val);
    void SetB(B *b);
    void Print();
};
#endif

//B.h
#ifndef B_H
#define B_H
class A;
class B
{
    double _val;
    A* _a;
public:

    B(double val);
    void SetA(A *a);
    void Print();
};
#endif

//A.cpp
#include "A.h"
#include "B.h"

#include <iostream>

using namespace std;

A::A(int val)
:_val(val)
{
}

void A::SetB(B *b)
{
    _b = b;
    cout<<"Inside SetB()"<<endl;
    _b->Print();
}

void A::Print()
{
    cout<<"Type:A val="<<_val<<endl;
}

//B.cpp
#include "B.h"
#include "A.h"
#include <iostream>

using namespace std;

B::B(double val)
:_val(val)
{
}

void B::SetA(A *a)
{
    _a = a;
    cout<<"Inside SetA()"<<endl;
    _a->Print();
}

void B::Print()
{
    cout<<"Type:B val="<<_val<<endl;
}

//main.cpp
#include "A.h"
#include "B.h"

int main(int argc, char* argv[])
{
    A a(10);
    B b(3.14);
    a.Print();
    a.SetB(&b);
    b.Print();
    b.SetA(&a);
    return 0;
}

Cảm ơn. Điều này giải quyết vấn đề dễ dàng. Tôi chỉ đơn giản là di chuyển thông tư bao gồm các tệp .cpp.
Lenar Hoyt

3
Điều gì nếu bạn có một phương pháp mẫu? Sau đó, bạn không thể thực sự chuyển nó vào tệp CPP trừ khi bạn khởi tạo các mẫu theo cách thủ công.
Malcolm

Bạn luôn bao gồm "Ah" và "Bh" cùng nhau. Tại sao bạn không bao gồm "Ah" trong "Bh" và sau đó chỉ bao gồm "Bh" trong cả "A.cpp" và "B.cpp"?
Gusev Slava

28

Tôi trả lời muộn, nhưng không có một câu trả lời hợp lý nào cho đến nay, mặc dù là một câu hỏi phổ biến với các câu trả lời được đánh giá cao ....

Thực hành tốt nhất: tiêu đề khai báo chuyển tiếp

Như được minh họa bởi tiêu <iosfwd>đề của thư viện Chuẩn , cách thích hợp để cung cấp khai báo chuyển tiếp cho người khác là có tiêu đề khai báo chuyển tiếp . Ví dụ:

a.fwd.h:

#pragma once
class A;

Ah:

#pragma once
#include "a.fwd.h"
#include "b.fwd.h"

class A
{
  public:
    void f(B*);
};

b.fwd.h:

#pragma once
class B;

bh:

#pragma once
#include "b.fwd.h"
#include "a.fwd.h"

class B
{
  public:
    void f(A*);
};

Mỗi người duy trì ABcác thư viện phải có trách nhiệm giữ các tiêu đề khai báo chuyển tiếp của họ đồng bộ với các tiêu đề và tệp thực thi của họ, vì vậy - ví dụ - nếu người duy trì "B" xuất hiện và viết lại mã thành ...

b.fwd.h:

template <typename T> class Basic_B;
typedef Basic_B<char> B;

bh:

template <typename T>
class Basic_B
{
    ...class definition...
};
typedef Basic_B<char> B;

... Sau đó, việc biên dịch lại mã cho "A" sẽ được kích hoạt bởi các thay đổi đối với bao gồm b.fwd.hvà sẽ hoàn thành sạch sẽ.


Thực hành kém nhưng phổ biến: chuyển tiếp công cụ trong libs khác

Nói - thay vì sử dụng tiêu đề khai báo chuyển tiếp như đã giải thích ở trên - mã trong a.hhoặc a.ccthay vào đó class B;tự khai báo :

  • nếu a.hhoặc a.ccđã bao gồm b.hsau:
    • việc biên dịch A sẽ chấm dứt với một lỗi khi nó xảy ra với khai báo / định nghĩa mâu thuẫn của B(nghĩa là thay đổi ở trên đối với B đã phá vỡ A và bất kỳ khách hàng nào khác lạm dụng khai báo chuyển tiếp, thay vì làm việc minh bạch).
  • mặt khác (nếu A cuối cùng không bao gồm b.h- có thể nếu A chỉ lưu trữ / chuyển xung quanh Bs bằng con trỏ và / hoặc tham chiếu)
    • công cụ xây dựng dựa trên #includephân tích và thay đổi dấu thời gian của tệp sẽ không được xây dựng lại A(và mã phụ thuộc thêm của nó) sau khi thay đổi thành B, gây ra lỗi tại thời gian liên kết hoặc thời gian chạy. Nếu B được phân phối dưới dạng DLL được tải trong thời gian chạy, mã trong "A" có thể không tìm thấy các ký hiệu được xử lý khác nhau trong thời gian chạy, có thể hoặc không thể xử lý đủ tốt để kích hoạt tắt máy theo thứ tự hoặc giảm chức năng chấp nhận được.

Nếu mã của A có các chuyên môn / "đặc điểm" mẫu cũ B, chúng sẽ không có hiệu lực.


2
Đây là một cách thực sự sạch sẽ để xử lý các tuyên bố chuyển tiếp. "Bất lợi" duy nhất sẽ là trong các tập tin bổ sung. Tôi giả sử bạn luôn bao gồm a.fwd.htrong a.h, để đảm bảo họ luôn đồng bộ. Mã ví dụ bị thiếu trong đó các lớp này được sử dụng. a.hb.hcả hai sẽ cần được đưa vào vì chúng sẽ không hoạt động riêng rẽ: `` `//main.cpp #include" ah "#include" bh "int main () {...}` `` Hoặc một trong số chúng cần phải được bao gồm đầy đủ trong khác như trong câu hỏi mở đầu. Trường hợp b.hbao gồm a.hmain.cppbao gồmb.h
Farway

2
@Farway Đúng trên tất cả các tính. Tôi không bận tâm đến việc hiển thị main.cpp, nhưng thật tuyệt vì bạn đã ghi lại những gì cần có trong bình luận của bạn. Chúc mừng
Tony Delroy

1
Một trong những câu trả lời tốt hơn với lời giải thích chi tiết thú vị về lý do tại sao với những điều đó và không nên do những ưu và nhược điểm ...
Francis Cugler

1
@RezaHajianpour: thật hợp lý khi có một tiêu đề khai báo chuyển tiếp cho tất cả các lớp mà bạn muốn khai báo chuyển tiếp, thông tư hay không. Điều đó nói rằng, bạn sẽ chỉ muốn chúng khi: 1) bao gồm khai báo thực tế là (hoặc có thể dự đoán sẽ trở thành sau này) tốn kém (ví dụ: nó bao gồm rất nhiều tiêu đề mà đơn vị dịch thuật của bạn có thể không cần) và 2) mã khách hàng là có khả năng có thể sử dụng các con trỏ hoặc tham chiếu đến các đối tượng. <iosfwd>là một ví dụ cổ điển: có thể có một vài đối tượng luồng được tham chiếu từ nhiều nơi và <iostream>có rất nhiều để bao gồm.
Tony Delroy

1
@RezaHajianpour: Tôi nghĩ rằng bạn có ý tưởng đúng, nhưng có một vấn đề thuật ngữ với tuyên bố của bạn: "chúng tôi chỉ cần loại được tuyên bố " sẽ đúng. Loại được khai báo có nghĩa là khai báo chuyển tiếp đã được nhìn thấy; nó được định nghĩa một khi định nghĩa đầy đủ đã được phân tích cú pháp (và vì thế bạn có thể cần nhiều #includes hơn ).
Tony Delroy

20

Những điều cần nhớ:

  • Điều này sẽ không hoạt động nếu class Acó một đối tượng class Blà thành viên hoặc ngược lại.
  • Chuyển tiếp khai báo là cách để đi.
  • Thứ tự khai báo các vấn đề (đó là lý do tại sao bạn chuyển ra các định nghĩa).
    • Nếu cả hai lớp gọi hàm của lớp kia, bạn phải di chuyển các định nghĩa ra.

Đọc Câu hỏi thường gặp:


1
các liên kết bạn cung cấp không hoạt động nữa, bạn có biết những liên kết mới để tham khảo không?
Ramya Rao

11

Tôi đã từng giải quyết loại vấn đề bằng cách di chuyển tất cả inlines sau khi định nghĩa lớp và đặt #includecho các lớp khác ngay trước khi inlines trong file header. Bằng cách này, người ta đảm bảo tất cả các định nghĩa + nội tuyến được đặt trước các nội tuyến được phân tích cú pháp.

Làm như thế này có thể vẫn có một loạt các dòng trong cả hai (hoặc nhiều) tệp tiêu đề. Nhưng nó là cần thiết để bao gồm bảo vệ .

Như thế này

// File: A.h
#ifndef __A_H__
#define __A_H__
class B;
class A
{
    int _val;
    B *_b;
public:
    A(int val);
    void SetB(B *b);
    void Print();
};

// Including class B for inline usage here 
#include "B.h"

inline A::A(int val) : _val(val)
{
}

inline void A::SetB(B *b)
{
    _b = b;
    _b->Print();
}

inline void A::Print()
{
    cout<<"Type:A val="<<_val<<endl;
}

#endif /* __A_H__ */

... và làm tương tự trong B.h


Tại sao? Tôi nghĩ rằng đó là một giải pháp tao nhã cho một vấn đề khó khăn ... khi một người muốn nội tuyến. Nếu một người không muốn nội tuyến, người ta không nên viết mã giống như được viết từ đầu ...
epatel

Điều gì xảy ra nếu một người dùng bao gồm B.hđầu tiên?
Ông Fooz

3
Lưu ý rằng bảo vệ tiêu đề của bạn đang sử dụng một mã định danh dành riêng, bất cứ thứ gì có dấu gạch dưới liền kề kép đều được bảo lưu.
Lars Viklund

6

Tôi đã viết một bài về điều này một lần: Giải quyết các phụ thuộc vòng tròn trong c ++

Kỹ thuật cơ bản là tách các lớp bằng các giao diện. Vì vậy, trong trường hợp của bạn:

//Printer.h
class Printer {
public:
    virtual Print() = 0;
}

//A.h
#include "Printer.h"
class A: public Printer
{
    int _val;
    Printer *_b;
public:

    A(int val)
        :_val(val)
    {
    }

    void SetB(Printer *b)
    {
        _b = b;
        _b->Print();
    }

    void Print()
    {
        cout<<"Type:A val="<<_val<<endl;
    }
};

//B.h
#include "Printer.h"
class B: public Printer
{
    double _val;
    Printer* _a;
public:

    B(double val)
        :_val(val)
    {
    }

    void SetA(Printer *a)
    {
        _a = a;
        _a->Print();
    }

    void Print()
    {
        cout<<"Type:B val="<<_val<<endl;
    }
};

//main.cpp
#include <iostream>
#include "A.h"
#include "B.h"

int main(int argc, char* argv[])
{
    A a(10);
    B b(3.14);
    a.Print();
    a.SetB(&b);
    b.Print();
    b.SetA(&a);
    return 0;
}

2
Xin lưu ý rằng việc sử dụng các giao diện và virtualcó tác động hiệu suất thời gian chạy.
cemper93

4

Đây là giải pháp cho các mẫu: Cách xử lý các phụ thuộc vòng tròn với các mẫu

Manh mối để giải quyết vấn đề này là khai báo cả hai lớp trước khi cung cấp các định nghĩa (triển khai). Không thể chia khai báo và định nghĩa thành các tệp riêng biệt, nhưng bạn có thể cấu trúc chúng như thể chúng nằm trong các tệp riêng biệt.


2

Ví dụ đơn giản được trình bày trên Wikipedia đã làm việc cho tôi. (bạn có thể đọc mô tả đầy đủ tại http://en.wikipedia.org/wiki/Circular_dependency#Example_of_circular_dependencies_in_C.2B.2B )

Tệp '' 'a.h' '':

#ifndef A_H
#define A_H

class B;    //forward declaration

class A {
public:
    B* b;
};
#endif //A_H

Tệp '' 'b.h' '':

#ifndef B_H
#define B_H

class A;    //forward declaration

class B {
public:
    A* a;
};
#endif //B_H

Tệp '' 'main.cpp' '':

#include "a.h"
#include "b.h"

int main() {
    A a;
    B b;
    a.b = &b;
    b.a = &a;
}

1

Thật không may, tất cả các câu trả lời trước đó đều thiếu một số chi tiết. Giải pháp đúng là hơi cồng kềnh, nhưng đây là cách duy nhất để làm điều đó đúng. Và nó dễ dàng thay đổi quy mô, xử lý các phụ thuộc phức tạp hơn là tốt.

Đây là cách bạn có thể làm điều này, chính xác giữ lại tất cả các chi tiết và khả năng sử dụng:

  • giải pháp hoàn toàn giống như dự định ban đầu
  • chức năng nội tuyến vẫn nội tuyến
  • người dùng ABcó thể bao gồm Ah và Bh theo bất kỳ thứ tự nào

Tạo hai tệp, A_def.h, B_def.h. Những điều này sẽ chỉ chứa A's và B' s định nghĩa:

// A_def.h
#ifndef A_DEF_H
#define A_DEF_H

class B;
class A
{
    int _val;
    B *_b;

public:
    A(int val);
    void SetB(B *b);
    void Print();
};
#endif

// B_def.h
#ifndef B_DEF_H
#define B_DEF_H

class A;
class B
{
    double _val;
    A* _a;

public:
    B(double val);
    void SetA(A *a);
    void Print();
};
#endif

Và sau đó, Ah và Bh sẽ chứa điều này:

// A.h
#ifndef A_H
#define A_H

#include "A_def.h"
#include "B_def.h"

inline A::A(int val) :_val(val)
{
}

inline void A::SetB(B *b)
{
    _b = b;
    _b->Print();
}

inline void A::Print()
{
    cout<<"Type:A val="<<_val<<endl;
}

#endif

// B.h
#ifndef B_H
#define B_H

#include "A_def.h"
#include "B_def.h"

inline B::B(double val) :_val(val)
{
}

inline void B::SetA(A *a)
{
    _a = a;
    _a->Print();
}

inline void B::Print()
{
    cout<<"Type:B val="<<_val<<endl;
}

#endif

Lưu ý rằng A_def.h và B_def.h là các tiêu đề "riêng tư", người dùng ABkhông nên sử dụng chúng. Tiêu đề công khai là Ah và Bh


1
Điều này có bất kỳ lợi thế so với giải pháp của Tony Delroy ? Cả hai đều dựa trên các tiêu đề "người trợ giúp", nhưng Tony nhỏ hơn (chúng chỉ chứa tuyên bố chuyển tiếp) và dường như chúng hoạt động theo cùng một cách (ít nhất là ở cái nhìn đầu tiên).
Fabio nói Phục hồi lại

1
Câu trả lời đó không giải quyết được vấn đề ban đầu. Nó chỉ nói "đưa các tuyên bố về phía trước thành một tiêu đề riêng biệt". Không có gì về việc giải quyết sự phụ thuộc vòng tròn (câu hỏi cần một giải pháp trong đó định nghĩa Acủa và Bcó sẵn, khai báo chuyển tiếp là không đủ).
geza

0

Trong một số trường hợp, có thể định nghĩa một phương thức hoặc hàm tạo của lớp B trong tệp tiêu đề của lớp A để giải quyết các phụ thuộc vòng tròn liên quan đến các định nghĩa. Theo cách này, bạn có thể tránh phải đặt các định nghĩa trong .cccác tệp, ví dụ nếu bạn muốn triển khai thư viện chỉ tiêu đề.

// file: a.h
#include "b.h"
struct A {
  A(const B& b) : _b(b) { }
  B get() { return _b; }
  B _b;
};

// note that the get method of class B is defined in a.h
A B::get() {
  return A(*this);
}

// file: b.h
class A;
struct B {
  // here the get method is only declared
  A get();
};

// file: main.cc
#include "a.h"
int main(...) {
  B b;
  A a = b.get();
}

0

Thật không may, tôi không thể bình luận câu trả lời từ geza.

Ông không chỉ nói "đưa các tuyên bố về một tiêu đề riêng biệt". Ông nói rằng bạn phải đổ các tiêu đề định nghĩa lớp và định nghĩa hàm nội tuyến vào các tệp tiêu đề khác nhau để cho phép "phụ thuộc bị trì hoãn".

Nhưng minh họa của anh ấy không thực sự tốt. Bởi vì cả hai lớp (A và B) chỉ cần một loại không hoàn chỉnh của nhau (trường con trỏ / tham số).

Để hiểu rõ hơn, hãy tưởng tượng rằng lớp A có trường loại B chứ không phải B *. Ngoài ra, lớp A và B muốn xác định hàm nội tuyến với các tham số thuộc loại khác:

Mã đơn giản này sẽ không hoạt động:

// A.h
#pragme once
#include "B.h"

class A{
  B b;
  inline void Do(B b);
}

inline void A::Do(B b){
  //do something with B
}

// B.h
#pragme once
class A;

class B{
  A* b;
  inline void Do(A a);
}

#include "A.h"

inline void B::Do(A a){
  //do something with A
}

//main.cpp
#include "A.h"
#include "B.h"

Nó sẽ dẫn đến mã sau đây:

//main.cpp
//#include "A.h"

class A;

class B{
  A* b;
  inline void Do(A a);
}

inline void B::Do(A a){
  //do something with A
}

class A{
  B b;
  inline void Do(B b);
}

inline void A::Do(B b){
  //do something with B
}
//#include "B.h"

Mã này không biên dịch vì B :: Do cần một loại A hoàn chỉnh được xác định sau.

Để đảm bảo rằng nó biên dịch mã nguồn sẽ như thế này:

//main.cpp
class A;

class B{
  A* b;
  inline void Do(A a);
}

class A{
  B b;
  inline void Do(B b);
}

inline void B::Do(A a){
  //do something with A
}

inline void A::Do(B b){
  //do something with B
}

Điều này là chính xác với hai tệp tiêu đề này cho mỗi lớp cần xác định các hàm nội tuyến. Vấn đề duy nhất là các lớp tròn không thể chỉ bao gồm "tiêu đề chung".

Để giải quyết vấn đề này, tôi muốn đề xuất một phần mở rộng tiền xử lý: #pragma process_pending_includes

Lệnh này sẽ trì hoãn việc xử lý tệp hiện tại và hoàn thành tất cả các chờ xử lý bao gồm.

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.