0

    class C {
    public
      T x;
    };

x 的构造函数是否有一种优雅的方法可以隐式地知道它正在构造的 C 实例是什么?


我已经用一些肮脏的不优雅的机器实现了这种行为。我的 sqlite3 包装器需要这个。我不喜欢我见过的所有包装器,它们的 API IMO 丑陋且不方便。我想要这样的东西:


    class TestRecordset: public Recordset {
    public:
      // The order of fields declarations specifies column index of the field.
      // There is TestRecordset* pointer inside Field class, 
      // but it goes here indirectly  so I don't have to 
      // re-type all the fields in the constructor initializer list.
      Field<__int64> field1;
      Field<wstring> field2;
      Field<double> field3;

      // have TestRecordset* pointer too so only name of parameter is specified
      // in TestRecordset constructor
      Param<wstring> param;

      virtual string get_sql() {
        return "SELECT 1, '1', NULL FROM test_table WHERE param=:PARAM";
      }

      // try & unlock are there because of my dirty tricks.
      // I want to get rid of them.

      TestRecordset(wstring param_value)
      try : Recordset(open_database(L"test.db")), param("PARAM") {
        param = param_value;
       // I LOVE RAII but i cant use it here. 
       // Lock is set in Recordset constructor, 
       // not in TestRecordset constructor.
        unlock(this);
        fetch();
      } catch(...) {
        unlock(this);
        throw;
      }
    };

I want to clarify the fact - it is a part of the working code. You can do this in C++. I just want to do it in a more nice way.


I've found a way to get rid of unlock and try block. I've remembered there is such a thing as thread local storage. Now I can write constructor as simple as that:

  TestRecordset(wstring param_value): 
    Recordset(open_database(L"test.db")), param("PARAM") {
        param = param_value;
        fetch();
   }


to dribeas: My objective is to avoid redundant and tedious typing. Without some tricks behind the scene I will have to type for each Field and Param:

TestRecordset(wstring param_value): Recordset(open_database(L"test.db")), param(this, "PARAM"),
   field1(this, 0), field2(this, 1), field3(this, 2) { ... }

它是多余的、丑陋的和不方便的。例如,如果我必须在 SELECT 中间添加新字段,我将不得不重写所有列号。关于您的帖子的一些注释:

  1. 字段和参数由它们的默认构造函数初始化。
  2. 构造函数中初始化程序的顺序无关紧要。字段始终按照其声明的顺序进行初始化。我已经使用这个事实来追踪字段的列索引
  3. 首先构造基类。因此,当构造字段时,Recordset 中的内部字段列表已准备好由 Filed 默认构造函数使用。
  4. 我不能在这里使用 RAII。我需要在 Recorset 构造函数中获取锁,并在构造所有字段后在 TestRecordset 构造函数中强制释放它。
4

5 回答 5

8

不,对象不应该需要知道它们从哪里被使用才能工作。就 x 而言,它是 T 的一个实例。就是这样。根据它是 C 类的成员、D 类的成员、自动的、临时的等,它的行为并没有什么不同。

此外,即使 T 构造函数确实知道 C 的实例,该 C 实例也是不完整的,因为它当然还没有完成构造,因为它的成员还没有被构造。C++ 为您提供了很多机会,但在另一个类的构造函数中为您提供对不完整对象的引用并不是其中之一。

我唯一能想到的近似您的代码示例的方法是执行类似的操作

#define INIT_FIELDS field1(this), field2(this), field3(this)

紧跟在字段列表之后,然后在初始化列表中使用 INIT_FIELDS 并 #undef 它。它仍然是重复的,但至少它都在一个地方。然而,这可能会让您的同事感到惊讶。

确保您不会忘记字段的另一种方法是从 Field 中删除零参数构造函数。同样,您仍然需要进行输入,但至少如果您忘记了某些内容,编译器会捕捉到它。我认为初始化列表的非 DRY 特性是 C++ 必须忍受的。

于 2008-12-03T18:46:52.493 回答
2

在逐个回答的基础上,您应该问的实际问题是:“我的解决方案设计有什么问题,它要求对象知道它们在哪里被实例化?”

于 2008-12-03T18:50:12.690 回答
1

我不这么认为。

出于纯粹的好奇,这有什么关系?你有一个可以使用的上下文吗?

M。

于 2008-12-03T18:48:14.800 回答
0

我对你的代码感兴趣。您评论说所有字段加上 param 属性都有指向 TestRecordSet 但它们不需要初始化的指针?还是问题的对象,如何避免在构造过程中必须传递 this 指针?

如果您想要避免在构造函数的初始化列表中添加所有字段,那么这是一个有缺陷的目标。您应该始终初始化初始化列表中的所有成员,并按照它们在类中声明的顺序执行此操作(这不是语言强制执行的,而是更多的全局学习经验)。

如果确实需要,您对 try 构造函数块的使用只是他仅推荐使用该功能(任何感兴趣的人阅读GOTW#66 )。如果 RecordSet 成员已经被构造(并因此获得了锁)并且随后在构造函数中出现问题,那么[见下面的引用] RecordSet 将被销毁,如果它在内部使用 RAII 它将释放锁,所以我相信try/catch 可能并不真正需要。

C++03、15.2 异常处理/构造函数和析构函数

部分构造或部分销毁的对象将为其所有完全构造的子对象执行析构函数,即构造函数已完成执行且析构函数尚未开始执行的子对象。

于 2008-12-03T20:36:32.910 回答
0

我一直在 C# 中尝试这样的事情——我使用反射来做这件事。

考虑为 C++ 获取反射或代码生成库来帮助您做您想做的事。

现在,我不能告诉你如何为 C++ 找到一个好的反射或代码生成库,但这是一个不同的问题!

于 2008-12-03T19:35:59.617 回答