C++构造函数中不需要的隐式转换
C++ Unwanted implicit conversion in constructor
我有一个简单的情况,我有一些统一的接口,比如:
class I {
public:
virtual void Run() = 0;
};
我有一些模板具有相同的接口,但出于性能原因,没有将其声明为虚拟接口。因此,我需要再添加一层,使其功能虚拟化:
template <class B>
class S : public I {
B mb;
public:
S(B b)
:mb(b)
{}
virtual void Run()
{
std::cout << mb << std::endl; // or mb.Run()
}
};
请注意,我通过打印mb.Run()
的值来替换它。这只是为了简化,它不会影响我在这里遇到的行为。但无论如何,到目前为止一切都很好。现在,为了方便起见,我又有一个类可以自动制作界面:
class A {
I *mi;
public:
A()
:mi(0)
{}
template <class B>
A(B b)
:mi(new S<B>(b))
{}
A(A &a)
:mi(a.mi)
{
a.mi = 0;
}
template <class B>
A &operator =(B b)
{
delete mi;
mi = new S<B>(b);
return *this;
}
A &operator =(A &a)
{
delete mi;
mi = a.mi;
a.mi = 0;
return *this;
}
I *operator ->()
{
assert(mi);
return mi;
}
};
这充当S
的自动构造函数,同时也充当简单的托管指针。我会按如下方式使用它:
A a = instanceOfB();
这应该调用模板A::A<instanceOfB>()
,后者又在堆上分配一个新的S<instanceOfB>
并将其存储在A
中。现在我可以调用A->Run()
,它最终解析为S->Run()
,然后调用instanceOfB::Run()
,它不是虚拟的。
相反,instanceOfB()
首先被转换为A
,然后它就死了,因为没有构造函数可以接受A
(只有A&
)。请注意,这种情况只发生在g++中,Visual Studio 2008和Visual C++6.0都可以顺利编译代码。您可以使用再现行为
void Test()
{
A a = 4; // error: no matching function for call to "A::A(A)"
//A a; a = 4; // works
//A a(4); // works
a->Run();
}
我曾尝试将构造函数声明为显式,但这似乎没有帮助,或者我可能做错了。如果A
不管理指针,我可以在构造函数中取const A&
的值,这样整个问题就会得到解决。这个问题还有别的解决办法吗?很遗憾,C++11不可用。
我正在努力实现高效的委派。基本上我想能够做到:
int myFunction(int, float);
StaticCallCtx<int, MakeTypelist(int, float)> ctx = Grab(&myFunction)(1, 2.3f);
// ctx.Run() calls the function, with the specified arguments
// it's *not* virtual (compiles to just a few instructions so I
// don't want to spoil it by adding a vfptr)
AutoCallPointer p = ctx;
// or directly AutoCallPointer p = Grab(&myFunction)(1, 2.3f);
// wraps StaticCallCtx, has ->Run() as well, this time there
// is the price of calling the virtual function
最终,高性能(稍后将用于加速一些线性代数函数)和用户舒适性(不编写模板参数列表的简短AutoCallPointer p = Grab(fun)(parms)
)是这里的主要目标。
编辑:
@ecatmur的解决方案是正确的。由于篇幅很短,我将尝试在这里重申。g++正确地拒绝编译代码,因为在A
中没有将采用A
的复制构造函数(只有A&
)。在复制初始化A a = instanceOfB()
的情况下,将不使用模板构造函数。
我们必须提供一个复制构造函数,采用const A&
。由于复制省略,构造函数的声明不带主体就足够了。然而,这不是一个好的解决方案。
最好将A::mi
声明为mutable
,并将现有的A&
构造函数更改为采用const A&
(复制运算符也可能更改)。固定的A
如下所示:
class A {
mutable I *mi;
public:
A()
:mi(0)
{}
template <class B>
A(B b)
:mi(new S<B>(b))
{}
A(const A &a)
:mi(a.mi)
{
a.mi = 0;
}
template <class B>
A &operator =(B b)
{
delete mi;
mi = new S<B>(b);
return *this;
}
A &operator =(const A &a)
{
delete mi;
mi = a.mi;
a.mi = 0;
return *this;
}
I *operator ->()
{
assert(mi);
return mi;
}
};
此代码可在g++和Microsoft的编译器中编译(也可在http://codepad.org/9FqUk0Fj)。
当您复制初始化类类型的对象时,复制构造函数需要可用,即使该复制被取消。g++拒绝您的程序是正确的;您的旧版本MSVC接受它是不正确的。
您可以在没有定义的情况下提供副本构造函数的声明,因为对它的调用将被忽略或在链接时失败。不过,这可能有些令人困惑。
最明显的解决方案是使用直接初始化,正如您已经观察到的那样,它运行良好。
- 在两个类中共享相同的函数调用,并在不需要时避免空实例化
- 是否有类似std::lower_bound的函数,而不需要排序/分区输入
- Visual C++(VS2017)中用户定义的转换不明确
- 为什么output_editor Concept不需要output_e迭代器标记
- 在除法中不需要四舍五入
- 与C代码相比,为什么C++代码不需要"#define _POSIX_C_SOURCE 200809L"?
- C++通常的算术转换不转换
- 字符类型转换不兼容
- 将值从另一个数组写入数组,不包括不需要的值 C++
- C++ 写入路径名中包含不需要的空字符的文件
- 是否有必要获取锁并在不需要唤醒线程时通知condition_variable?
- 使用 assimp 加载模型 - 不需要提升?
- 为什么转换函数声明不需要至少一个定义类型说明符
- 返回不需要的值的二叉搜索程序
- Win32 键转换代码输出不需要的按键
- 为什么 new 不需要强制转换为指针,即使 malloc 需要它?
- C++ - 标准::地图 不需要强制转换的替代方案
- 安全,检查后强制转换为c++基类,不需要额外的行
- C++构造函数中不需要的隐式转换
- c++:包含对象的确切类型,不需要强制类型转换