构造函数重载和默认参数

Constructor overloading and default parameters

本文关键字:参数 默认 重载 构造函数      更新时间:2023-10-16

我正在学习使用对象和继承,并且在为派生类编写构造函数时遇到了一些麻烦。

基类是一个简单的"项目",带有标题、作者、项目编写年份和一些注释。我希望在构建项目时标题和作者字段是强制性的,但另一个是可选的。所以我编写了一个构造函数,其中包含两个可选的参数。

// item.hpp
class Item
{
public:
    std::string title;
    std::string author;
    unsigned int year;
    std::string notes;
    Item(const std::string &t,
         const std::string &a,
         const unsigned int y = 0,
         const std::string &n = "")
    : title{t}, author{a}, year{y}, notes{n}
    { };
}

现在我有一个派生类:"Book",我正在尝试编写构造函数,但不断收到"调用 Book 的构造函数不明确"的错误

// book.hpp
class Book : public Item
{
public:
    unsigned int pages;
    std::string series;
    // Fill only the fields for Item
    Book(const std::string &t,
         const std::string &a,
         const unsigned int y = 0,
         const std::string &n = "")
    : Item(t, a, y, n)
    { };
    // Fill all the fields for Book
    Book(const std::string &t,
         const std::string &a,
         const unsigned int y = 0,
         const std::string &n = "",
         const unsigned int p = 0,
         const std::string &s = "")
    : Item(t, a, y, n), pages{p}, series{s}
    { };
    // Maybe there's no notes
    Book(const std::string &t,
         const std::string &a,
         const unsigned int y = 0,
         const unsigned int p = 0,
         const std::string &s = "")
    : Item(t, a, y), pages{p}, series{s}
    { };
}

现在,如果我尝试写一本这样的书:

Book b("a", "b");

我收到构造函数调用不明确的错误。

我的问题是:我应该如何解决"希望有多个具有默认参数的构造函数,这样我就不必填充所有参数"的问题?

我认为可以删除"Book"的第一个构造函数,因为它是第二个构造函数的"子集"。但我不知道如何处理第三个。有什么建议吗?

编辑:我也曾想过建造空对象,稍后b.member = whatever填充所有成员,但这似乎不是很好。

提前谢谢。

Book类的所有构造函数都具有相同数量和类型的非默认参数。这意味着,当您只提供这些时,编译器无法判断要使用其中哪一个,并且会给您一个不明确的调用错误。您实际上只需要一个默认了其余部分,因为只有当您确实要填写某些内容时,您才会调用其他构造函数,因此请尝试从除一个构造函数之外的所有构造函数中删除默认值。

发生这种情况是因为您有 2 个或更多 Book 个可以使用 Book("a", "b") 调用的构造函数。此问题与默认参数没有直接关系。即使其中一个构造函数具有所有参数的默认值,也会发生此错误。应该只有一个可以使用两个字符串调用的构造函数。

为了证明此问题与非默认参数的数量无关,这将导致相同的错误,因为这些构造函数也可以使用两个字符串调用:

// Can be called with two string args:
Book(const std::string &t = "",
     const std::string &a = "")
: Item(t, a, 0, "")
{ };
// This can also be called with two string args:
Book(const std::string &t,
     const std::string &a = "")
: Item(t, a, 0, "")
{ };

从设计的角度来看,您的 3 个构造函数似乎是多余的,它们或多或少具有相同的参数和不同的顺序。在您的情况下,解决方案将仅使用一个具有最多参数的构造函数,并组合所有可能的参数。对默认参数进行排序,以便最常用的参数位于参数列表中的较低索引中。

我个人讨厌有很多参数的函数和构造函数。如果参数数量达到大约 5 个或更多,我通常推荐以下模式:

创建一个结构,其中包含作为成员变量的函数调用的所有参数。将结构体的 const 引用作为构造函数或函数的唯一参数传递给:

struct SBookInfo
{
    std::string t;
    std::string a;
    unsigned int y;
    std::string n;
    SBookInfo()
    {
        y = 0;
    }
    // TODO: provide static NAMED factory
    // methods that don't have thousands of arguments
    static SBookInfo CreateWhatever(unsigned int _y=0)
    {
        SBookInfo info;
        info.a = "whatever";
        info.t = "woof";
        info.y = _y;
        return info;
    }
}
// Book constructor
Book(const SBookInfo& info);

这样,使用您的代码的人就不必担心参数顺序之类的东西,并且构造Book实例的代码在任何地方都变得更加清晰和可读:

// With this pattern you can give default value to any args.
// The user of the constructor can specify the args in any order.
SBookInfo info;
info.y = 6;
info.a = "woof";
info.t = "woof";
Book book(info);
// For some special cases the info struct can have NAMED
// factory methods that makes the code more readable.
Book book2(SBookInfo.CreateWhatever(5));
// The above code is much more obvious to read than
// the original. By reading this code who could tell
// me the name of the 3rd argument where we pass 6 to the ctor?
// Book book("woof", "woof", 6);
// And it would become even cleaner with growing number of args.

另一个建议:永远不要使用at等名称。避免更简单的速记。只是不值得节省输入几个字符。无论如何,对于现代 IDE,您只需在第一次命名标识符时键入名称,以后的自动完成可帮助您避免键入。

w/委派构造函数:

Book(const std::string &t,
     const std::string &a)
: Book(t, a, 0, "")
{ }
Book(const std::string &t,
     const std::string &a,
     const unsigned int y,
     const unsigned int p = 0,
     const std::string &s = "")
: Book(t, a, y, "", p, s)
{ }
Book(const std::string &t,
     const std::string &a,
     const unsigned int y,
     const std::string &n,
     const unsigned int p = 0,
     const std::string &s = "")
: Item(t, a, y, n), pages{p}, series{s}
{ }

没有委派构造函数:

Book(const std::string &t,
     const std::string &a)
: Item(t, a), pages{0}, series{""}
{ }
Book(const std::string &t,
     const std::string &a,
     const unsigned int y,
     const unsigned int p = 0,
     const std::string &s = "")
: Item(t, a, y), pages{p}, series{s}
{ }
Book(const std::string &t,
     const std::string &a,
     const unsigned int y,
     const std::string &n,
     const unsigned int p = 0,
     const std::string &s = "")
: Item(t, a, y, n), pages{p}, series{s}
{ }
class Item {
protected:
    std::string mTitle;
    std::string mAuthor;
    std::string mNotes;
    unsigned int mYear;
public:
    Item( const std::string& title, const std::string& author, unsigned int year = 0, const std::string& notes = std::string() );        
};
Item::Item( const std::string& title, const std::string& author, unsigned int year, const std::string& notes ) :
mTitle( title ),
mAuthor( author ),
mYear( year ),
mNotes( notes ) {
} // Item
class Book : public Item {
private:
    std::string mSeries;
    unsigned int mPages;
public:
    Book( const std::string& title, const std::string& author, unsigned int year = 0, unsigned int pages = 0,  const std::string& series = 0, const std::string& notes = std::string() );       
};
Book::Book( const std::string& title, const std::string& author, unsigned int year, unsigned int pages, const std::string& series, std::string& notes ) :
Item( title, author, year, notes ),
mSeries( series ),
mPages( pages ) {
} // Book

现在,我确实使 Item 的成员受到保护,以便任何派生类(如 Book)都可以直接访问它们,并且 Book I 中的成员设为私有,因此您需要访问器函数或方法来修改或从外部对象或代码源检索这些成员。

编辑在本书的构造函数中,我稍微改变了参数的顺序。我将页数向左移动,并将注释移到默认参数的参数调用的右侧。我这样做是因为更有可能在有可用注释之前使用可用页数调用此构造函数,并且我还移动了系列并将注释保留为最后一个参数。

int main() {
    // Different Ways To Call this using default parameters
    Book b1( "Fellowship of the Ring", "Tolkien", 1958, 387, "Lord of the Rings" ); // No Notes
    Book b2( "Apostle of John" "John", 27, 80, std::string() or " ", "A book of the New Testament found in the Holy Bible" ); // Notes, but not series.
    return 0;
} // main