【问题标题】:A nice way to initialise template classes to a base class将模板类初始化为基类的好方法
【发布时间】:2013-03-14 00:00:20
【问题描述】:

我知道这个问题可能非常模糊,它是对我给 here 的答案的扩展。基本上,作为一个个人项目,我一直在尝试复制“代码优先”的习语,这在 C# 程序员中很流行做数据库工作,但在 C++ 中。一切都很棒,而且运作良好。

C# 的一大优点是属性等。我们在 C++ 中没有这种新颖性,所以我的特定类的代码然后在数据库中结构化,如下所示:

class User : public Record {
public:
    User(Model& m) : Record(L"_user", m)
                   , Username(L"_userName", 32, false)
                   , Nickname(L"_nickName", 32, false) {
        field(Username);
        field(Nickname);
    };  // eo ctor

    Field<String> Username;
    Field<String> Nickname;
};  // eo class User

理想情况下,我想摆脱构造函数中的field 调用。本质上,他们所做的只是向基类注册该字段,然后由基类完成真正的工作。我怎样才能完成这种事情,并将细节传递给基类,就像我在构造函数中指定的那样简单?

I answered 的上一个问题中,由于 C++ 构造事物的方式,我能够提供基类信息。有没有办法可以在构造函数中利用它并摆脱可能会导致程序员错误的讨厌的field 调用?

在这一点上,我很高兴一切都运行良好,但我很想减少混乱并消除构造函数中这种“注册”过程的需要,并以某种方式让它们自动注册到基类中。

欢迎所有意见:)

编辑

field的实现是这样的:

        void Record::field(detail::field_base& field) {
            field.owner_ = this;
            fields_.push_back(&field);
        };  // eo field**

【问题讨论】:

  • 如果你能展示field() 的实际作用以及它是哪个类的成员(我猜是Record,但这只是一个猜测),这将有所帮助
  • @AndyProwl,我已经修改了这个问题。除了将其推送到列表中以供稍后推荐之外,它几乎没有什么作用。

标签: c++ visual-c++ c++11


【解决方案1】:

您可以让Field&lt;&gt; 的构造函数接受一个指向Record 的附加指针,并在构造函数主体中对该指针调用field(*this)

template<typename T>
struct Field // Just guessing...
{
    Field(std::string s, int i, bool b, Record* pRecord) 
    // ...
    { 
        pRecord->field(*this); 
    }
    // ...
};

然后,在 User 的初始化列表中,您将传递 this 作为附加的 Record 指针,这样:

User(Model& m) : Record(L"_user", m)
               , Username(L"_userName", 32, false, this)
               , Nickname(L"_nickName", 32, false, this)
{
};  // eo ctor

【讨论】:

  • 哇,我可能遗漏了一些东西,但是...我在脑海中想到 this 在初始化部分无效...当然,这很可能是我的错误。
  • @Moo-Juice:在Record的构造函数被调用时,基子对象已经被构造(成员是在基类之后构造的),所以这应该是格式正确的。
  • @Moo-Juice 在构造过程中小心调用virtual 的函数(即使是间接调用!)。并且避免做可能失败的事情,除非你帮不上忙(在施工过程中处理失败很烦人)
【解决方案2】:

只需将必要的信息(即您的 this 指针)传递给 Field 构造函数,然后让它完成工作

你甚至可以为此引入一个特殊的包装类

【讨论】:

    【解决方案3】:

    不要纠结于属性语法。

    您想要的是能够将structs 视为具有运行时反射的有意义命名数据的聚合,对吗?

    要引入新名称,请使用类型:

    template<typename T> struct FieldTag {};
    struct Username:FieldTag<Username> {} username;
    struct Nickname:FieldTag<Nickname> {} nickname;
    

    要生成类型的聚合,请使用模板参数:

    template<typename Child, typename Field>
    struct FieldHandler {
      typedef typename Field::tag_type tag_type;
      typedef typename Field::value_type value_type;
      PsuedoRef<value_type> operator[]( tag_type ) { /* details */ }
      /* details */
    };
    template<typename... Fields>
    struct Aggregate:
      Record,
      FieldHandler<Aggregate<Fields...>, Fields>...
    {
      /* details */
    };
    template<typename Value, typename Tag>
    struct Field {
      typedef Tag tag_type;
      typedef Value value_type;
    };
    

    上述所有样板的目标,加上一些操作符覆盖滥用,是我们可以从中得到一些漂亮的语法,最终程序员得到一个看起来很像没有样板的 C++ 的语法。

    以下是您如何创建具有两个字段的 Data 类型的最终目标:

    struct Data:
      Aggregate<
        Field<std::string, Username>,
        Field<std::string, Nickname>
      >
    {
      Data(): Aggregate(
        username|L"username" = L"Unknown Name",
        nickname|L"nickname" = L"Unknown NickName"
      {}
    };
    

    要访问字段,请使用适当覆盖的运算符:(我喜欢^,它绑定紧密,看起来有点像-&gt;

    Data d;
    d^username= L"Bob";
    d^nickname= L"Apples";
    

    这在 C++11 中都是可能的,但并不是那么容易。

    现在,将指向函数的指针作为模板参数的能力,以及可转换为函数的无状态 lambda,可能让我们将字符串存储在字段的类型中,无需在构造时传递它.另一方面,对未评估上下文的限制使这看起来值得怀疑。

    所以剩下的就是丑陋的一点。另外,完成上述工作所需的努力让我认为您可能应该吞下每个字段上烦人的样板。

    请注意,上面的代码只是伪代码,是您可以采取的路线的草图,并不是所有接近完成的代码。我建议不要尝试走这条路,除非你想迷失在学习 C++ 的模板元编程子语言的树林中,并且永远无法解决你的问题。

    话说回来,学习模板元编程难道不会比您当前的项目更有趣吗? ;)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-11-10
      • 2021-04-23
      • 1970-01-01
      • 1970-01-01
      • 2021-01-03
      • 2016-08-08
      • 1970-01-01
      • 2022-01-05
      相关资源
      最近更新 更多