c++ 未输入派生类复制构造函数,而复制构造函数采用父类引用

rsl1atfo  于 2023-03-14  发布在  其他
关注(0)|答案(1)|浏览(164)

考虑层次结构

base_dir -> base_dir_ext -> server
                       \--> location

下面的简化代码包含一个方法,该方法通过继承,有时接受server,有时接受location,并且需要从它示例化location。使用下面的main(),一切工作正常,并且进入location复制构造函数。

#include <iostream>

class base_dir
{
    // shared inherited member
    public:
        base_dir() { };
        virtual ~base_dir() { };
};

class base_dir_ext : public base_dir
{
    // even more shared inherited members
    public:
        base_dir_ext() { };
        virtual ~base_dir_ext() { };
        base_dir_ext(const base_dir_ext &other);
        base_dir_ext(const base_dir &other);
};

class server : public base_dir_ext
{
    public:
        server() { };
        ~server() { };
        server(const server &other);
        // server(const base_dir &other);
        server &operator=(const server &other);
};

class location : public base_dir_ext
{
    public:
        location() { };
        ~location() { };
        location(const location&) : base_dir_ext()
        {
            std::cout << "COPY-CTOR FOR LOCATION ENTERED" << std::endl;
        }

        location(const base_dir&)
        {
            std::cout << "COPY-CTOR FOR BASE_DIR ENTERED" << std::endl;
        }

        location(const server&) : base_dir_ext()
        {
            std::cout << "COPY-CTOR FOR SERVER ENTERED" << std::endl;
        }
};

base_dir *process_location(base_dir *parent)
{
    std::cout << "typeid: " << typeid(*(dynamic_cast<location*>(parent) ? dynamic_cast<location*>(parent) : parent)).name() << std::endl;
    location loc((dynamic_cast<location*>(parent) ? dynamic_cast<location&>(*parent) : dynamic_cast<server&>(*parent)));
    // process location and add it to `parent`
    return (parent);
}

int main()
{
    location loc;

    base_dir *parent = &loc;

    process_location(parent);
}

输出:

typeid: 8location
COPY-CTOR FOR LOCATION ENTERED

但是,正如您可能已经注意到的,我不得不注解掉server::server(const base_dir &other),即server的接受base_dir的构造函数。当我取消注解它时,

#include <iostream>

class base_dir
{
    // shared inherited member
    public:
        base_dir() { };
        virtual ~base_dir() { };
};

class base_dir_ext : public base_dir
{
    // even more shared inherited members
    public:
        base_dir_ext() { };
        virtual ~base_dir_ext() { };
        base_dir_ext(const base_dir_ext &other);
        base_dir_ext(const base_dir &other);
};

class server : public base_dir_ext
{
    public:
        server() { };
        ~server() { };
        server(const server &other);
        server(const base_dir &other);
        server &operator=(const server &other);
};

class location : public base_dir_ext
{
    public:
        location() { };
        ~location() { };
        location(const location&) : base_dir_ext()
        {
            std::cout << "COPY-CTOR FOR LOCATION ENTERED" << std::endl;
        }

        location(const base_dir&)
        {
            std::cout << "COPY-CTOR FOR BASE_DIR ENTERED" << std::endl;
        }

        location(const server&) : base_dir_ext()
        {
            std::cout << "COPY-CTOR FOR SERVER ENTERED" << std::endl;
        }
};

base_dir *process_location(base_dir *parent)
{
    std::cout << "typeid: " << typeid(*(dynamic_cast<location*>(parent) ? dynamic_cast<location*>(parent) : parent)).name() << std::endl;
    location loc((dynamic_cast<location*>(parent) ? dynamic_cast<location&>(*parent) : dynamic_cast<server&>(*parent)));
    // process location and add it to `parent`
    return (parent);
}

int main()
{
    location loc;

    base_dir *parent = &loc;

    process_location(parent);
}

则生成编译器错误:

$ c++ -Wall -Wextra -Werror main.cpp
main.cpp: In function 'base_dir* process_location(base_dir*)':
main.cpp:54:48: error: operands to ?: have different types 'location' and 'server'  location loc((dynamic_cast<location*>(parent) ? dynamic_cast<location&>(*parent) : dynamic_cast<server&>(*parent)));
                ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:54:48: note:   and each type can be converted to the other

我的问题是:为什么会发生这种情况?为什么突然之间serverlocation看起来是不同的类?它们不认为是等价的,因为它们有相同的父类和祖类吗?
为了消除这个错误,我尝试在process_location()中删除parentserver&类型的动态强制转换:

#include <iostream>

class base_dir
{
    // shared inherited member
    public:
        base_dir() { };
        virtual ~base_dir() { };
};

class base_dir_ext : public base_dir
{
    // even more shared inherited members
    public:
        base_dir_ext() { };
        virtual ~base_dir_ext() { };
        base_dir_ext(const base_dir_ext &other);
        base_dir_ext(const base_dir &other);
};

class server : public base_dir_ext
{
    public:
        server() { };
        ~server() { };
        server(const server &other);
        server(const base_dir &other);
        server &operator=(const server &other);
};

class location : public base_dir_ext
{
    public:
        location() { };
        ~location() { };
        location(const location&) : base_dir_ext()
        {
            std::cout << "COPY-CTOR FOR LOCATION ENTERED" << std::endl;
        }

        location(const base_dir&)
        {
            std::cout << "COPY-CTOR FOR BASE_DIR ENTERED" << std::endl;
        }

        location(const server&) : base_dir_ext()
        {
            std::cout << "COPY-CTOR FOR SERVER ENTERED" << std::endl;
        }
};

base_dir *process_location(base_dir *parent)
{
    std::cout << "typeid: " << typeid(*(dynamic_cast<location*>(parent) ? dynamic_cast<location*>(parent) : parent)).name() << std::endl;
    location loc((dynamic_cast<location*>(parent) ? dynamic_cast<location&>(*parent) : *parent));
    // process location and add it to `parent`
    return (parent);
}

int main()
{
    location loc;

    base_dir *parent = &loc;

    process_location(parent);
}

但是,尽管typeid()明确地输出location类型,location的const base_dir&构造函数被调用,而不是它自己的复制构造函数。

typeid: 8location
COPY-CTOR FOR BASE_DIR ENTERED

为什么只有server(const base_dir &other);会破坏三进制呢?我不希望删除这个方法,因为我希望我的类是通用的。我是否遗漏了继承中的什么东西,使我的代码无法增加?我更愿意保留第一个版本的代码,其中的服务器构造函数方法不加注解:这可能吗?

wbgh16ku

wbgh16ku1#

错误消息的重要部分是最后一句话:

main.cpp:54:48: note:   and each type can be converted to the other

看起来你并不知道条件运算符实际上是做什么的。它不是if-else的替代品。这将编译没有问题(在添加server(const base_dir &other)之后):

if (auto p = dynamic_cast<location*>(parent)) {
    location loc(*p);
    // process location and add it to `parent`
    return parent;
} else {
    location loc(dynamic_cast<server&>(*parent));
    // process location and add it to `parent`
    return parent;
}

Live Demo
然而,这还是太复杂了。与其进行这样的强制转换,然后有条件地调用一个或另一个构造函数,不如构造函数引用base,然后在内部进行检查。
条件运算符的问题在于条件运算符的类型是最后两个操作数的公共类型。

location loc((dynamic_cast<location*>(parent) ? dynamic_cast<location&>(*parent) : dynamic_cast<server&>(*parent)));

它是location&server&server可以通过location::location(const server&)转换为locationlocation可以通过server::server(const base_dir &other)转换为server。因此,生成错误消息。如果没有server::server(const base_dir&)转换构造函数,则条件运算符的结果类型为location&,这实际上使得在这里使用它毫无意义。你不能像这样对动态强制转换使用条件运算符。
有关详细信息,请参考https://en.cppreference.com/w/cpp/language/operator_otherhttps://en.cppreference.com/w/cpp/types/common_type
下面是一个简单的示例,演示条件运算符如何扰乱强制转换:

#include <iostream>

struct base {
    virtual ~base() {}
};
struct foo : base {};
struct bar : base {
    bar() = default;
    bar(const foo&) {}
};

void what_is_it(const base&) {std::cout << "base"; }
void what_is_it(const foo&) { std::cout << "foo"; }
void what_is_it(const bar&) { std::cout << "bar"; }

int main() {
    foo a;
    base& x = a;
    what_is_it( dynamic_cast<bar*>(&x) ? (std::cout << "true",dynamic_cast<bar&>(x)) : dynamic_cast<foo&>(x));
    std::cout << "\n";

    bar b;
    base& y = b;
    what_is_it( dynamic_cast<bar*>(&y) ? (std::cout << "true",dynamic_cast<bar&>(y)) : dynamic_cast<foo&>(y));
}

Output

bar 
truebar

因为对于x,强制转换失败,而对于y,强制转换成功。尽管在这两种情况下,条件运算符都使结果成为bar&,而不管它之前被强制转换为的类型是什么。bar&foo&的公共类型是bar&,因为X1 M19 N1 X可以被转换为X1 M20 N1 X(但反之亦然)。

相关问题