2017-09-18 47 views
4

我想創建一個項目的一些類的命名參數類構造函數。C++命名參數與派生類的實現

我這樣做的方式是通過定義一個類代理來保存參數並將此代理的一個實例傳遞給我的類的構造函數。

一切工作正常,直到我不得不派生我的一個類。

基本上我想:我要從基類代理派生新的派生類代理。這也適用,但只適用於僅使用派生代理類參數的情況。

下面是一個例子,因爲它是更易於理解:

class Person 
{ 
public: 
    class PersonArgs 
    { 
    public: 
     const std::string& Name() const { return _name; } 
     PersonArgs& Name(const std::string& name) 
     { 
      _name = name; 
      return *this; 
     } 

     const std::string& Surname() const { return _surname; } 
     PersonArgs& Surname(const std::string& surname) 
     { 
      _surname = surname; 
      return *this; 
     } 

    protected: 
     std::string _name; 
     std::string _surname; 
    } 

public: 
    Person() 
     : _name("") 
     , _surname("") 
    { } 

    Person(const PersonArgs& args) 
     : _name(args.Name()) 
     , _surname(args.Surname()) 
    { } 

protected: 
    std::string _name; 
    std::string _surname; 
} 

class PersonEx : public Person 
{ 
public: 
    class PersonExArgs : public Person::PersonArgs 
    { 
    public: 
     const std::string& Address() const { return _address; } 
     PersonExArgs& Address(const std::string& address) 
     { 
      _address = address; 
      return *this; 
     } 

    protected: 
     std::string _address; 
    } 

public: 
    PersonEx() 
     : _address("") 
    { } 

    PersonEx(const PersonExArgs& args) 
     : Person(args) 
     , _address(args.Address()) 
    { } 

protected: 
    std::string _address; 
} 

int main(int argc, char** argv) 
{ 
    // This is ok since PersonExArgs::Address returns a PersonExArgs& 
    PersonEx* p1 = new PersonEx(PersonEx::PersonExArgs().Address("example")); 

    // This won't work since PersonExArgs::Name returns a PersonArgs& 
    PersonEx* p2 = new PersonEx(PersonEx::PersonExArgs().Address("example").Name("Mark")); 
} 

基本上,由於我連鎖返回到代理類實例的引用,當我設定的參數的參數,它使用此時中斷從派生的代理類,因爲它將返回對基類代理類的引用,而不是派生類的引用,不允許我訪問派生代理參數,也不會將其傳遞給派生類的構造函數。

任何人都有如何解決這個問題的想法?

+0

沒有真正解決這個問題,但不應該更輕量級的* builder *類在這種情況下更合適?通過「更輕量級」,我的意思是它不存儲要創建的類包含的同一組字段。這是它在內部創建新的類實例,直接填充它的字段,然後返回一個指向釋放所有權的新類實例的指針。 'PersonEx * p1 = Make ().Address(「example」)。Name(「Mark」)。Done();' – VTT

+0

@VTT無需返回指針;直接返回對象並讓編譯器使用RVO優化它(如果它不能使用RVO,則優化它以移動) – Justin

+0

這整個想法是不好的。在上面的用例中,地址和名稱將調用副本。只要寫一些合理的構造函數。 –

回答

1

最常見的解決這個問題是奇異遞歸模板模式(CRTP):

template <typename Derived> 
class PersonArgs 
{ 
public: 
    const std::string& Name() const { return _name; } 
    Derived& Name(const std::string& name) 
    { 
     _name = name; 
     return static_cast<Derived&>(*this); 
    } 

    const std::string& Surname() const { return _surname; } 
    Derived& Surname(const std::string& surname) 
    { 
     _surname = surname; 
     return static_cast<Derived&>(*this); 
    } 

protected: 
    std::string _name; 
    std::string _surname; 
}; 

... 

class PersonExArgs : public Person::PersonArgs<PersonExArgs> 
{ 
public: 
    const std::string& Address() const { return _address; } 
    PersonExArgs& Address(const std::string& address) 
    { 
     _address = address; 
     return *this; 
    } 

protected: 
    std::string _address; 
}; 

在你的情況,你可以用另一個基類結合起來收拾接口:

class Person { 
    class PersonArgsBase 
    { 
    public: 
     const std::string& Name() const { return _name; } 
     const std::string& Surname() const { return _surname; } 

    protected: 
     std::string _name; 
     std::string _surname; 
    }; 

    template <typename Derived> 
    class PersonArgs : public PersonArgsBase 
    { 
     Derived& Name(const std::string& name) 
     { 
      _name = name; 
      return static_cast<Derived&>(*this); 
     } 

     Derived& Surname(const std::string& surname) 
     { 
      _surname = surname; 
      return static_cast<Derived&>(*this); 
     } 
    }; 

    ... 
}; 

class PersonEx { 
    class PersonExArgs : public Person::PersonArgs<PersonExArgs> 
    { 
     ... 
    }; 
}; 
+0

在給定的表格中,使用PersonArgs將會產生一個代碼膨脹,因爲每個新的特化都將是一個具有兩個字段和四個函數的獨立類。然而,如果'PersonArgs'派生自包含這些字段和四個函數的非模板基類,但是不會從派發該作業的設置器返回任何內容給'PersonArgs'類,該類將只有兩個setter函數,每個調用基類setter並返回正確的派生類型的引用。這樣'PersonArgs'模板類將保持輕量級。 – VTT

+0

@VTT這是不是我所建議的「你可以將它與類型擦除」 – Justin

+1

我不知道,因爲你的'PersonArgsBase'和派生類型沒有內容,你沒有說明任何推理後面介紹他們。此外,我不提議在PersonArgsBase虛擬中創建任何函數,或者使PersonArgsBase成爲模板本身(實際上這是應該避免的)。 – VTT

1

也許covariant返回類型是你在找什麼。
有關更多詳細信息,請參閱here


您可以定義PersonArgs爲(注意virtual關鍵字周圍放置):

class PersonArgs 
{ 
public: 
    const std::string& Name() const { return _name; } 
    virtual PersonArgs& Name(const std::string& name) 
    { 
     _name = name; 
     return *this; 
    } 

    const std::string& Surname() const { return _surname; } 
    virtual PersonArgs& Surname(const std::string& surname) 
    { 
     _surname = surname; 
     return *this; 
    } 

protected: 
    std::string _name; 
    std::string _surname; 
}; 

然後定義PersonExArgs爲(注意override和協變返回類型):

class PersonExArgs : public Person::PersonArgs 
{ 
public: 
    const std::string& Address() const { return _address; } 
    PersonExArgs& Address(const std::string& address) 
    { 
     _address = address; 
     return *this; 
    } 

    PersonExArgs& Name(const std::string& name) override 
    { 
     PersonArgs::Name(name); 
     return *this; 
    } 

    PersonExArgs& Surname(const std::string& surname) override 
    { 
     PersonArgs::Surname(surname); 
     return *this; 
    } 

protected: 
    std::string _address; 
}; 

也許它是煩人的你必須重寫基類中的每一個函數,但它很好地完成了工作。
查看並運行於wandbox