Chuyển tiếp khai báo các kiểu / lớp lồng nhau trong C ++


197

Gần đây tôi đã bị mắc kẹt trong một tình huống như thế này:

class A
{
public:
    typedef struct/class {...} B;
...
    C::D *someField;
}

class C
{
public:
    typedef struct/class {...} D;
...
    A::B *someField;
}

Thông thường bạn có thể khai báo tên lớp:

class A;

Nhưng bạn không thể chuyển tiếp khai báo một kiểu lồng nhau, các nguyên nhân sau đây gây ra lỗi biên dịch.

class C::D;

Có ý kiến ​​gì không?


6
Tại sao bạn cần điều đó? Lưu ý rằng bạn có thể chuyển tiếp khai báo nếu đó là thành viên của cùng một lớp được xác định: class X {class Y; Y * a; }; lớp X :: Y {};
Julian Schaub - litb

Giải pháp này hiệu quả với tôi (không gian tên C {class D;};): stackoverflow.com/questions/22389784/
Kẻ

Tôi tìm thấy một liên kết
bitlixi

Câu trả lời:


224

Bạn không thể làm điều đó, đó là một lỗ hổng trong ngôn ngữ C ++. Bạn sẽ phải hủy lồng ít nhất một trong các lớp lồng nhau.


6
Cảm ơn câu trả lời. Trong trường hợp của tôi, chúng không phải là các lớp lồng nhau của tôi. Tôi đã hy vọng tránh được sự phụ thuộc tệp tiêu đề thư viện lớn với một chút tham chiếu về phía trước. Tôi tự hỏi nếu C ++ 11 sửa nó?
Marsh Ray

61
Oh. Chỉ là những gì tôi đã không muốn google hiển thị. Dù sao cũng cảm ơn câu trả lời ngắn gọn.
tìm hiểu

19
Tương tự ở đây ... có ai biết tại sao điều đó là không thể? Có vẻ như có các trường hợp sử dụng hợp lệ và việc thiếu này ngăn cản tính nhất quán của kiến ​​trúc trong một số tình huống.
Maël Nison

Bạn có thể sử dụng bạn bè. Và chỉ cần đưa ra một nhận xét rằng bạn đang sử dụng nó để khắc phục lỗ hổng trong C ++.
Erik Aronesty

3
Bất cứ khi nào tôi gặp phải những sai sót không cần thiết như vậy trong ngôn ngữ ersatz này, tôi bị giằng xé giữa cười và khóc
SongWithoutemme

33
class IDontControl
{
    class Nested
    {
        Nested(int i);
    };
};

Tôi cần một tài liệu tham khảo về phía trước như:

class IDontControl::Nested; // But this doesn't work.

Cách giải quyết của tôi là:

class IDontControl_Nested; // Forward reference to distinct name.

Sau này khi tôi có thể sử dụng định nghĩa đầy đủ:

#include <idontcontrol.h>

// I defined the forward ref like this:
class IDontControl_Nested : public IDontControl::Nested
{
    // Needed to make a forwarding constructor here
    IDontControl_Nested(int i) : Nested(i) { }
};

Kỹ thuật này có thể sẽ rắc rối hơn giá trị của nó nếu có các hàm tạo phức tạp hoặc các hàm thành viên đặc biệt khác không được kế thừa trơn tru. Tôi có thể tưởng tượng ma thuật mẫu nhất định phản ứng xấu.

Nhưng trong trường hợp rất đơn giản của tôi, nó dường như làm việc.


16
Trong C ++ 11, bạn có thể kế thừa các hàm tạo using basename::basename;trong lớp dẫn xuất, do đó không có vấn đề gì với các hàm phức tạp.
Xèo

1
Thủ thuật hay, nhưng nó sẽ không hoạt động nếu con trỏ tới IDontControl :: Nested được sử dụng trong cùng một tiêu đề (nơi nó được chuyển tiếp khai báo) và được truy cập từ mã bên ngoài cũng bao gồm định nghĩa đầy đủ về IDontControl. (Vì trình biên dịch sẽ không khớp với IDontControl_Nested và IDontControl :: Nested). Cách giải quyết là thực hiện đúc tĩnh.
Artem Pisarenko

Tôi khuyên bạn nên làm ngược lại và có lớp bên ngoài, và chỉ sử dụng typedefbên trong lớp
Ridderhoff

3

Nếu bạn thực sự muốn tránh # bao gồm tệp tiêu đề khó chịu trong tệp tiêu đề của mình, bạn có thể làm điều này:

tập tin hpp:

class MyClass
{
public:
    template<typename ThrowAway>
    void doesStuff();
};

tập tin cpp

#include "MyClass.hpp"
#include "Annoying-3rd-party.hpp"

template<> void MyClass::doesStuff<This::Is::An::Embedded::Type>()
{
    // ...
}

Nhưng sau đó:

  1. bạn sẽ phải chỉ định loại nhúng tại thời điểm cuộc gọi (đặc biệt nếu chức năng của bạn không nhận bất kỳ tham số nào của loại được nhúng)
  2. chức năng của bạn không thể là ảo (vì nó là một mẫu)

Vì vậy, yeah, đánh đổi ...


1
Cái quái gì là một hpptập tin?
Naftali aka Neal

7
lol, một tệp tiêu đề .hpp được sử dụng trong các dự án C ++ để phân biệt với tệp tiêu đề C thường kết thúc bằng .h. Khi làm việc với C ++ và C trong cùng một dự án, một số người thích .hpp và .cpp cho các tệp C ++, để làm cho nó rõ ràng với loại tệp nào mà họ xử lý và .h và .c cho các tệp C.
cắn

2

Điều này có thể được thực hiện bằng cách chuyển tiếp khai báo lớp bên ngoài như một không gian tên .

Ví dụ: Chúng ta phải sử dụng một lớp lồng nhau khác :: A :: Nested in other_a.h, nằm ngoài tầm kiểm soát của chúng ta.

những người khác_a.h

namespace others {
struct A {
    struct Nested {
        Nested(int i) :i(i) {}
        int i{};
        void print() const { std::cout << i << std::endl; }
    };
};
}

my_group.h

#ifndef MY_CLASS_CPP
// A is actually a class
namespace others { namespace A { class Nested; } }
#endif

class MyClass {
public:
    MyClass(int i);
    ~MyClass();
    void print() const;
private:
    std::unique_ptr<others::A::Nested> _aNested;
};

my_group.cpp

#include "others_a.h"
#define MY_CLASS_CPP // Must before include my_class.h
#include "my_class.h"

MyClass::MyClass(int i) :
    _aNested(std::make_unique<others::A::Nested>(i)) {}
MyClass::~MyClass() {}
void MyClass::print() const {
    _aNested->print();
}

1
Nó có thể hoạt động, nhưng nó không có giấy tờ. Lý do tại sao nó hoạt động là a::bđược xử lý theo cùng một cách, bất kể alà lớp hay không gian tên.
jaskmar

3
Không hoạt động với Clang hoặc GCC. Nó nói rằng lớp bên ngoài được khai báo là một cái gì đó khác với một không gian tên.
Dugi

1

Tôi sẽ không gọi đây là câu trả lời, nhưng dù sao cũng là một phát hiện thú vị: Nếu bạn lặp lại tuyên bố về cấu trúc của mình trong một không gian tên gọi là C, mọi thứ đều ổn (ít nhất là trong gcc). Khi định nghĩa lớp của C được tìm thấy, nó dường như âm thầm ghi đè lên không gian tên C.

namespace C {
    typedef struct {} D;
}

class A
{
public:
 typedef struct/class {...} B;
...
C::D *someField;
}

class C
{
public:
   typedef struct/class {...} D;
...
   A::B *someField;
}

1
Tôi đã thử điều này với cygwin gcc và nó không biên dịch nếu bạn cố gắng tham khảo A.someField. C :: D trong lớp Định nghĩa A thực sự đề cập đến cấu trúc (trống) trong không gian tên, không phải cấu trúc trong lớp C (BTW này không được biên dịch trong MSVC)
Cá heo

Nó báo lỗi: "'lớp C' được phân phối lại dưới dạng loại biểu tượng khác"
Calmarius

9
Trông giống như một lỗi GCC. Dường như nghĩ rằng một tên không gian tên có thể ẩn một tên lớp trong cùng phạm vi.
Julian Schaub - litb

0

Đây sẽ là một cách giải quyết (ít nhất là cho vấn đề được mô tả trong câu hỏi - không phải cho vấn đề thực tế, tức là khi không có quyền kiểm soát định nghĩa về C):

class C_base {
public:
    class D { }; // definition of C::D
    // can also just be forward declared, if it needs members of A or A::B
};
class A {
public:
    class B { };
    C_base::D *someField; // need to call it C_base::D here
};
class C : public C_base { // inherits C_base::D
public:
    // Danger: Do not redeclare class D here!!
    // Depending on your compiler flags, you may not even get a warning
    // class D { };
    A::B *someField;
};

int main() {
    A a;
    C::D * test = a.someField; // here it can be called C::D
}

0

Nếu bạn có quyền truy cập để thay đổi mã nguồn của lớp C và D, thì bạn có thể tách riêng lớp D và nhập một từ đồng nghĩa cho nó trong lớp C:

class CD {

};

class C {
public:

    using D = CD;

};

class CD;
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.