2015-05-27 2 views
2

Файл Dog.hC++ #include Loop

#ifndef DOG_H 
#define DOG_H 

#include "Cat.h" 
//class Cat; 

class Dog 
{ 
     Cat c; 
}; 

#endif 

Файл Cat.h

#ifndef CAT_H 
#define CAT_H 

#include "Dog.h" 
//class Dog; 

class Cat 
{ 
     Dog d; 
}; 

#endif 

Это вызывает цикл, проблема заключается в том, что оба класса должен знать друг о друге ; форвардная декларация также не решает проблему. Вы можете создать указатель для Dog d или Cat c для его решения.

Вопрос: Не существует способа не создавать указатель, т. Е. Сохранить код как есть, не переписывая, как я его хочу?

+1

почему опережающее объявление не решить ее? – dlavila

+0

Ответ: Нет. Как бы вы построили объект, который содержит себя? Вы также должны переосмыслить свой дизайн, если считаете, что вам нужно такое. – StoryTeller

+0

Это не может быть так, как вы хотите, поскольку, даже если вы можете разрешить порядок объявления, для каждого класса логически невозможно скрыть другой. Нужно иметь указатель или ссылку; и что не нужно включать заголовок. –

ответ

5

То, что вы пытаетесь сделать, невозможно. Ваш класс занял бы бесконечное пространство.

Вы получили Dog, который имеет переменную Cat члена, который имеет переменную Dog члена, который имеет переменную Cat члена, который имеет переменный с Dog членом ...

Не нарушая его где-то с указателем вы В конечном итоге бесконечные рекурсивные переменные-члены берут бесконечную память, что невозможно.

Обратите внимание, что компилятор не может даже рассчитать память, необходимую для Dog или Cat из-за бесконечной рекурсии. Но так как класс в C++ не может быть размером 0, мы знаем, что это будет бесконечное пространство.

Другие ответы также дают хорошие перспективы, т. Е. Проблема «Курица и яйцо», из которой нужно определить сначала, чтобы определить другую.


Я просто хочу добавить, что взаимно рекурсивные классы нечто странное и лучше избегать, если вы можете создать его другим способом. Если вам действительно нужно их использовать, убедитесь, что вы действительно нарушите рекурсивность. В противном случае вы меняете его только с ошибкой во время компиляции из-за бесконечной взаимной рекурсии на исчерпание памяти и сбоя во время выполнения, если вы получили наивную реализацию с указателями, которые совершают бесконечные взаимно-рекурсивные вызовы new во время построения.

+0

это правда. Но я думаю, что это не причина ошибки, посмотрите на мой ответ (пожалуйста, поправьте меня, если я ошибаюсь) –

1

Нет, это невозможно, и это не имеет никакого отношения к файлам.

Причина в том, что если вы создадите класс, который содержит другой класс как член (а не указатель), компилятору необходимо добавить некоторое пространство для этого класса в другой класс. Например, если вы создаете что-то вроде этого:

class Dog { 
    std::string name; 
    std::string owner; 
}; 

class Person { 
    Dog pet; 
    std::string name; 
}; 

Затем экземпляры класса Person в приведенном выше примере имеют размер, который sizeof(std::string) + sizeof(Dog), в то время как экземпляры класса Dog имеют размер, который sizeof(std::string) * 2; так Личность sizeof(std::string) * 3 по размеру (выравнивание по модулю различия)

Другими словами, объем памяти, необходимый для переменной первого класса (в отличие от указателя) в C++ присваивается как часть экземпляра, и не является указатель; это работает очень точно так же, как и struct.Фактически, фактически нет практической разницы между ключевыми словами class и struct в C++, кроме того, что по умолчанию класс private, тогда как структура - public.

1

Помимо этого, ведущего к бесконечному пространственному объекту (как объясняет Рафаэль), компилятор не допустит, чтобы это произошло, как описано в этом документе question. Он не знает о Cat класса, когда вы используете его в Dog классе, поэтому он не будет работать по той же причине, что это не будет работать:

class B; 
class A{ 
    B b; 
}; 
+1

Еще один способ увидеть это. Я предполагаю, что это можно было бы расширить до проблемы «курица и яйцо», поскольку один класс должен быть определен первым, прежде чем вы сможете определить другое, но вы не можете сказать, какой из них. –

+0

@RaphaelMiedl Correct –

1

Как уже отмечалось, вы не можете напрямую или косвенно определить объект, который сам по себе является членом без косвенности.

Если вы хотите использовать синтаксис . вместо синтаксиса ->, вы можете использовать ссылку вместо указателя. Например:

// in Dog.h 
class Cat; 

class Dog { 
public: 
    std::unique_ptr<Cat> catp; 
    Cat &c; 
    Dog(); 
    Dog (Cat &); 
}; 

// in Cat.h 
class Cat { 
public: 
    std::unique_ptr<Dog> dogp; 
    Dog &d; 
    Cat(); 
    Cat (Dog &); 
} 

Теперь в исходном коде, вы можете сделать что-то вроде этого:

#include "Dog.h" 
#include "Cat.h" 

Dog::Dog() : catp(new Cat(*this)), c(*catp) {} 
Cat::Cat() : dogp(new Dog(*this)), d(*dogp) {} 

Dog::Dog (Cat &cat) : c(cat) {} 
Cat::Cat (Dog &dog) : d(dog) {} 
+0

Я просто хочу добавить предупреждение, что это следует рассматривать только как пример. Наивная реализация только сдвинет проблему времени компиляции на бесконечные взаимно-рекурсивные «новые» вызовы во время построения. Позаботьтесь о том, чтобы у вас действительно был способ нарушить рекурсию. –