C ++中的相互类引用

时间:2018-12-01 21:13:08

标签: c++ architecture software-design

我公司的一些代码采用以下格式:

class ObjectA {
public:
  ObjectA(ObjectB &objectB): objectB(objectB) { }

void Initialize() { ... }

private:
  ObjectB &objectB;
};

class ObjectB {
public:
  ObjectB(ObjectA &objectA): objectA(objectA) { }

void Initialize() { ... }

private:
  ObjectA &objectA;
};

基本上,两个类相互依赖。

这本身并不是困扰我的事情,(尽管这不是一个很好的设计IMO)这是在两种情况下相互依赖都通过引用传递给构造函数 >。

因此,实际构造这对对象的唯一方法(在应用程序中它们都是必需的)是将不完整的引用作为依赖项传递。为了解决这个问题,将单独的Initialize方法添加为第二阶段构造函数,然后在创建两个对象之后调用它们。因此,构造函数通常除了将构造函数参数分配给对象内部并用Intizialize方法初始化所有内容外,什么都不做。

尽管代码可能是有效的,但我倾向于出于以下原因,这是一个有缺陷的设计:

  1. 不能确保依赖对象的状态 构造函数
  2. 这要求开发人员额外致电Initialize
  3. 它排除了使用编译器警告来检查成员变量是否已初始化
  4. 没有什么可以阻止Initialize被多次调用的,这可能会导致奇怪的,难以跟踪的错误

我的同事告诉我,使用单独的initialize方法可以简化对象的构造,因为开发人员不必担心在应用程序根目录中声明的对象顺序。可以以完全任意的顺序声明和构造它们,因为一旦调用Initialize保证一切都有效。

我一直在倡导对象构造应该遵循依赖关系图的相同顺序,并且应该重新设计这些对象以消除相互依赖关系,但是我想看看SO专家必须说些什么。我以为这是不好的做法我错了吗?

编辑:

这些类的实际构造方式是通过如下所示的工厂类:

Factory {
public:
  Factory():
    objectA(objectB),
    objectB(objectA) {
  }
private:
  ObjectA objectA;
  ObjectB objectB;
};

2 个答案:

答案 0 :(得分:1)

这是不好的做法,是的。通过objectB引用objectA可以使ObjectA在尚未正确初始化的情况下工作,这是不可以的。

现在它可能是符合标准和安全的代码,因为ObjectB不会尝试在其构造函数中访问传递给objectB的引用,但是安全性被线程挂起了。如果以后有人决定只是在构造函数中进行初始化,或者访问objectB中的任何内容,或者以其他方式更改class DB_Connect { private MySqlConnection connection; private string server; private string database; private string uid; private string password; //Constructor public DB_Connect() { Initialize(); } //Initialize values private void Initialize() { server = "localhost"; database = "xyz"; uid = "root"; password = ""; string connectionString = $"datasource=127.0.0.1;port = 3306; SERVER={server}; DATABASE={database}; USERNAME={uid}; PASSWORD={password};sslmode=none"; connection = new MySqlConnection(connectionString); } //open connection to database public bool OpenConnection() { try { connection.Open(); return true; } catch (MySqlException ex) { switch (ex.Number) { case 0: MessageBox.Show("Cannot connect to server. Contact administrator"); break; case 1045: MessageBox.Show("Invalid username/password, please try again"); break; } return false; } } //Close connection public bool CloseConnection() { try { connection.Close(); return true; } catch (MySqlException ex) { MessageBox.Show(ex.Message); return false; } } } 的使用方式,那么您最终会得到UB。

这听起来像是两个构造函数都运行后重新定位的指针的用例。

答案 1 :(得分:1)

我也不喜欢所发布的代码-它不必要地复杂且脆弱。当两个类像这样合作时,通常会有所有权的概念,因此,如果类型为A的对象拥有类型为B的对象(他们可能应该这样做),那么您将执行以下操作:

#include <memory>

class A;

class B
{
public:
    B (A& a) : a (a) { }
private:
    A& a;
};

class A
{
public:
    A () { b = std::make_unique <B> (*this); }

private:
    std::unique_ptr <B> b;
}; 

Live demo