Как получить доступ к функциям-членам объекта класса из другого объекта класса, который был создан в нем?

#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).