#c #class #object #member
Вопрос:
class Class1 //Would be object mClass1
{
public:
void Function1()
{
a ;
}
private:
int a = 0;
Class2 mClass2;
}
(Редактирование в пробел здесь, чтобы уточнить, что Class2 не определен после Class1; они находятся в отдельных файлах.)
class Class2 //Would be object mClass2
{
public:
Function2()
{
Function1(); // Would be from mClass1
}
}
Таким образом, Class1 создает экземпляр объекта Class2, и этот объект Class2 имеет функцию-член, которая хочет получить доступ к функции-члену «родительского» объекта без использования наследования.
Я не знаю, что мне конкретно нужно искать, чтобы узнать об этом. Имеет ли это отношение к разыменованию new
указателя? Тип / инициализация конструктора? Есть ли у него какая-то терминология? «Вложенные классы» выводят классы, определенные внутри другого класса, а это не то, что есть на самом деле.
Ответ №1:
Без наследования невозможно получить «родительский класс». Поэтому вместо этого вы должны просто передать функцию в качестве параметра, возможно, в конструкторе класса 2, если вы используете его несколько раз. Смотрите, например: https://www.cprogramming.com/tutorial/function-pointers.html
Ответ №2:
Вы не можете этого сделать. Class2
еще не известно, когда вы определяете Class1
, поэтому элемент Class1::mClass2
данных не может быть создан. Но эта проблема может быть решена путем определения Class2
до Class1
и реализации Class2::Function2()
вне класса и только после Class1
.
Что касается вызова Function1()
внутри Function2()
, Class2
необходимо знать объект, к которому нужно обращаться Function1()
. Для этого можно использовать ссылочный элемент, который вы инициализируете в конструкторе:
// Forward-declaration of Class1 so that Class2 will be able to define
// references or pointers to Class1.
class Class1;
class Class2
{
public:
// Constructor that requires a reference to our parent object.
explicit Class2(Class1amp; parent)
: parent_(parent)
{ }
// Just declare the function. We need to implement it later, outside
// this class definition because Class1 is not fully known yet and as
// a result we can't have calls to Function1() because the compiler
// doesn't know that function yet.
void Function2();
private:
// This is just a reference, so it works even if Class1 is not fully
// known yet.
Class1amp; parent_;
};
class Class1
{
public:
void Function1() { /* ... */ }
private:
int a = 0;
Class2 mClass2{*this}; // Pass ourself as the parent object.
};
// Class1 is fully known now, so we can do calls to Function1().
inline void Class2::Function2()
{
parent_.Function1();
}
Это будет работать, но имеет важное значение: оно отключает оператор присваивания Class2
. Вероятно, это то, что вы хотите в данном случае, потому что две копии Class2
, вероятно, не должны иметь один и тот же Class1
родительский объект.
Однако я не понимаю, зачем вам это нужно делать. Это усложняет ситуацию без уважительной причины. Почему бы просто не передать Class1
объект, который Function2()
должен использоваться в качестве аргумента функции вместо этого? Итак:
class Class1;
class Class2
{
public:
void Function2(Class1amp; c1_obj);
};
class Class1
{
public:
void Function1() { /* ... */ }
private:
int a = 0;
Class2 mClass2;
};
inline void Class2::Function2(Class1amp; c1_obj)
{
c1_obj.Function1();
}
Поэтому всякий Class1
раз, когда требуется вызвать Class2::Function2()
, просто перейдите *this
к нему. Это проще и не имеет недостатков хранения ссылки или указателя на другой объект.
Комментарии:
1. Я получаю «неизвестный спецификатор переопределения» @
Class2 mClass2{*this};
, плюс восемь связанных ошибок после этого. Может быть, потому, что я не использую встроенный. Необходимо ли это?2. @Wicky работает нормально: godbolt.org/z/qMeWenjz9 И да,
inline
это необходимо для функций, реализованных в заголовочных файлах, но вне класса. (Кроме шаблонов функций, они не нужныinline
.) Если вы реализуете функцию в.cpp
файле, тоinline
это не требуется.
Ответ №3:
С каноническими классами — нет способа сделать это, потому Class2
что является неполным внутри Class1
, и если вы объявите Class2
внутри Class1
(как вложенный класс), у него не будет доступа Class1
, потому Class1
что неполный!
Похоже на неразрешимый парадокс? Это неразрешимо в области ООП, но можно обойти, как показал Никос. Но проблема неопределенных типов в некоторых случаях может быть решена в C или аналогичных концептуально-ориентированных языках с помощью CRTP — любопытно повторяющегося шаблона.
Возможно ли это или нет в вашем случае использования и насколько сложным это будет, зависит от того, какую цель вы преследуете. Вот пример парадоксального поведения CRTP — член базового класса может вызывать член производного класса:
#include <iostream>
template < class T>
class Base {
public:
template <class U>
struct Accessor : public U {
static void evoke_foo( Tamp; obj)
{
return (obj.*(static_cast< void(T::*)() >(amp;Accessor::foo))) ();
}
};
void evoke( )
{
Accessor<T>::evoke_foo( *static_cast<T*>(this) );
}
};
class Derived : public Base<Derived> {
protected:
void foo() { std::cout << "Foo is called" << std::endl; }
};
int main()
{
Derived a;
a.evoke(); // evoke belongs to base.
}
Теперь, если бы мы захотели определить возвращаемый тип foo()
автоматически здесь, это стало бы безумно сложным фрагментом кода. Некоторые подобные проблемы решаются в реализациях стандартного одноименного evoke
метода.
Комментарии:
1. «нет способа сделать это, потому что Class2 является неполным внутри Class1» На самом деле это просто. Просто определите
Class2
передClass1
😛 (см. Мой ответ.)2. @Nikos тогда у него не было бы доступа к
Class1
тому, как показывал OP. В некоторых случаях работает предварительное разъяснение и разделение определения объявления (и это обычный способ сделать это), но есть случаи, когда это невозможно. Ссылочный подход часто оборачивается в CRTP (это делают компоненты std с помощью reference_wrapper и trait class)3. Ну, обычно вы всегда разделяете объявление и определение в любом случае. Вы объявляете свои классы в
.h
файлах и определяете «тяжелые» функции в.cpp
файле, а «легкие» функцииinline
— в самом низу.h
файла. Это был наиболее распространенный метод в C в течение некоторого времени. Большинство людей не хотят засорять API класса определениями функций.4. @NicosC верно, для той части, что альтернатива не была хорошо разработана в большинстве компиляторов, хотя это было проблемой для стандартных компонентов и их заголовочного характера. Только C 20 решил эту проблему (и добавил новые проблемы), введя модули. И было неясно, что имел в виду OP. в вашей версии Class2 и Class1 являются отдельными объектами, это не плоский объект. В моем случае Base является аналогом Class2 и является подобъектом Derived (Class1).