Meneruskan deklarasi tipe / kelas bersarang di C ++

197

Saya baru-baru ini terjebak dalam situasi seperti ini:

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

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

Biasanya Anda dapat mendeklarasikan nama kelas:

class A;

Tapi Anda tidak bisa meneruskan menyatakan tipe bersarang, berikut ini menyebabkan kesalahan kompilasi.

class C::D;

Ada ide?

Calmarius
sumber
6
Mengapa Anda membutuhkannya? Perhatikan bahwa Anda dapat meneruskan menyatakan jika itu adalah anggota dari kelas yang sama sedang didefinisikan: kelas X {kelas Y; Y * a; }; kelas X :: Y {};
Johannes Schaub - litb
Solusi ini bekerja untuk saya (namespace C {class D;};): stackoverflow.com/questions/22389784/…
Albert Wiersch
Saya menemukan tautan
bitlixi

Jawaban:

224

Anda tidak dapat melakukannya, itu adalah lubang dalam bahasa C ++. Anda harus melepaskan sarang setidaknya dari salah satu kelas bersarang.

Adam Rosenfield
sumber
6
Terima kasih atas jawabannya. Dalam kasus saya, mereka bukan kelas saya. Saya berharap untuk menghindari ketergantungan file header perpustakaan yang besar dengan sedikit referensi ke depan. Saya ingin tahu apakah C ++ 11 memperbaikinya?
Marsh Ray
61
Oh Hanya apa yang saya tidak ingin google muncul. Terima kasih atas jawaban singkatnya.
learnvst
19
Sama di sini ... apakah ada yang tahu mengapa itu tidak mungkin? Tampaknya ada kasus penggunaan yang valid, dan kekurangan ini mencegah konsistensi arsitektur dalam beberapa situasi.
Maël Nison
Anda bisa menggunakan teman. Dan cukup beri komentar bahwa Anda menggunakannya untuk bekerja di sekitar lubang di C ++.
Erik Aronesty
3
Setiap kali saya menemukan kekurangan yang tidak perlu dalam bahasa ersatz ini, saya terpecah antara tertawa dan menangis
SongWithoutWords
33
class IDontControl
{
    class Nested
    {
        Nested(int i);
    };
};

Saya membutuhkan referensi ke depan seperti:

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

Solusi saya adalah:

class IDontControl_Nested; // Forward reference to distinct name.

Kemudian ketika saya bisa menggunakan definisi lengkap:

#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) { }
};

Teknik ini mungkin akan lebih merepotkan daripada nilainya jika ada konstruktor yang rumit atau fungsi anggota khusus lainnya yang tidak diwarisi dengan lancar. Saya bisa membayangkan sihir template tertentu bereaksi buruk.

Tetapi dalam kasus saya yang sangat sederhana, tampaknya berhasil.

Marsh Ray
sumber
16
Di C ++ 11 Anda bisa mewarisi konstruktor using basename::basename;di dalam kelas turunan, sehingga tidak ada masalah dengan ctors yang rumit.
Xeo
1
Trik yang bagus, tetapi itu tidak akan berfungsi jika pointer ke IDontControl :: Nested digunakan dalam header yang sama (yang dinyatakan maju) dan diakses dari kode eksternal yang juga menyertakan definisi lengkap IDontControl. (Karena kompiler tidak akan cocok dengan IDontControl_Nested dan IDontControl :: Nested). Solusinya adalah dengan melakukan cast statis.
Artem Pisarenko
Saya akan merekomendasikan melakukan yang sebaliknya dan memiliki kelas di luar, dan cukup gunakan typedefdi dalam kelas
ridderhoff
3

Jika Anda benar-benar ingin menghindari #termasuk file header jahat di file header Anda, Anda bisa melakukan ini:

file hpp:

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

file cpp

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

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

Tapi kemudian:

  1. Anda harus menentukan jenis tertanam pada waktu panggilan (terutama jika fungsi Anda tidak mengambil parameter dari jenis tertanam)
  2. fungsi Anda tidak bisa virtual (karena itu adalah templat)

Jadi, yeah, pengorbanan ...

jembatan eden
sumber
1
Apa yang dimaksud dengan hppfile?
Naftali alias Neal
7
lol, file header .hpp digunakan dalam proyek C ++ untuk membedakannya dari file header C yang biasanya diakhiri dengan .h. Ketika bekerja dengan C ++ dan C dalam proyek yang sama beberapa orang lebih suka .hpp dan .cpp untuk file C ++, untuk membuatnya secara eksplisit dengan jenis file apa yang mereka hadapi, dan .h dan .c untuk file C.
bitek
2

Ini dapat dilakukan dengan meneruskan mendeklarasikan kelas luar sebagai namespace .

Contoh: Kita harus menggunakan kelas bersarang lain :: A :: Bersarang di others_a.h, yang di luar kendali kami.

others_a.h

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

my_class.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_class.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();
}
bitlixi
sumber
1
Mungkin berhasil, tetapi tidak berdokumen. Alasan mengapa itu bekerja adalah yang a::bhancur dengan cara yang sama, tidak peduli apakah akelas atau namespace.
jaskmar
3
Tidak bekerja dengan Dentang atau GCC. Dikatakan bahwa kelas luar dinyatakan sebagai sesuatu yang berbeda dari namespace.
Dugi
1

Saya tidak akan menyebut ini sebagai jawaban, tetapi tetap saja merupakan temuan yang menarik: Jika Anda mengulangi deklarasi struct Anda dalam namespace yang disebut C, semuanya baik-baik saja (setidaknya dalam gcc). Ketika definisi kelas dari C ditemukan, tampaknya secara diam-diam menimpa namspace 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;
}
nschmidt
sumber
1
Saya mencoba ini dengan cygwin gcc dan tidak dapat dikompilasi jika Anda mencoba referensi A.someField. C :: D di kelas Definisi sebenarnya mengacu pada struct (kosong) di namespace, bukan struct di kelas C (BTW ini tidak dikompilasi dalam MSVC)
Dolphin
Ini memberikan kesalahan: "'kelas C' dideklarasikan ulang sebagai jenis simbol yang berbeda"
Calmarius
9
Sepertinya bug GCC. Tampaknya berpikir nama namespace dapat menyembunyikan nama kelas dalam lingkup yang sama.
Johannes Schaub - litb
0

Ini akan menjadi solusi (setidaknya untuk masalah yang dijelaskan dalam pertanyaan - bukan untuk masalah aktual, yaitu, ketika tidak memiliki kendali atas definisi 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
}
chtz
sumber
0

Jika Anda memiliki akses untuk mengubah kode sumber kelas C dan D, maka Anda dapat mengambil kelas D secara terpisah, dan memasukkan sinonim untuknya di kelas C:

class CD {

};

class C {
public:

    using D = CD;

};

class CD;
Suvorov Ivan
sumber