问题描述
是否有可能实现(至少类似于)这个? 我需要“命名参数”的指定初始值设定项和/或跳过某些参数设置的可能性(此处未显示)。并且仍然得到这个“级联”默认值。
理想情况下,我需要在不了解继承的情况下设置派生(实例化时)的参数(因为应该说 5 级继承,其用户不友好必须知道有多少继承......)当然参数的知识需要名称和顺序。
#include <iostream>
using namespace std;
struct Base
{
string baseDefault = "Default";
string label = "Base";
};
struct Derived : public Base
{
// using Base::baseDefault; // do not help
using Base::Base;
string label = "Derived";
};
int main()
{
Derived d1{.baseDefault="ChangedDefault",.label="NewDerived"};
Derived d2{};
Base b1{.label="NewBase"};
Base b2{};
cout << " d1: " << d1.label << d1.baseDefault
<< ",d2: " << d2.label << d2.baseDefault
<< ",b1: " << b1.label << b1.baseDefault
<< ",b2: " << b2.label << b2.baseDefault << endl;
/* expect: d1: NewDerived ChangedDefault,d2: Derived Default,b1: NewBase Default,b2: Base Default
*/
return 0;
}
我试着澄清一下:
struct Base
{
string withDefault = "baseDefault";
string noDefault;
};
struct Derived : public Base
{
string inDerived; /* no matter with/without default*/
};
int main()
{
Derived d{{.noDefault="SomeSetting"},.inDerived="NextSetting"};
Base b{.nodefault="SomeSetting"};
return 0;
}
但问题是: 如果我构造了 /Derived/,我需要为 /withDefault/ 使用不同的默认值。所以像这样:
struct Derived : public Base
{
string withDefault = "useThisAsDefaultHere";
string inDerived; /* no matter with/without default*/
};
解决方法
考虑以下“幼稚”的设计:
#include <iostream>
struct Base {
char const* Base_var1 = "Base_var1";
char const* Base_var2;
};
struct Derived1 : public Base {
char const* Base_var1 = "Derived1_var1";
char const* derived1_var3 = "Derived1_var3";
};
struct Derived2 : public Derived1 {
char const* derived2_var4 = "Derived2_var4";
char const* derived2_var5 = "Derived2_var5";
};
这里我们有五个变量,分别以 _var1
、_var2
、_var3
、_var4
和 _var5
结尾。它们的前缀是它们第一次在其中定义的类的名称。例如,Base
定义了 Base_var1
。尽管 Derived1
覆盖了默认值,但它当然在 Derived1
中仍然具有相同的名称。
因此我们可以声明 _var1
在 Base
中有一个默认值,它在 Derived1
中被覆盖。 _var2
没有默认值,分别在 _var3-5
和 Derived1
中引入了 Derived2
,并带有默认值。
如果现在我们想构造一个 Derived2
类型的对象,我们想在其中使用所有默认值,除了 _var3
和 _var5
(当然还给 _var2
一个值)然后我们可以尝试这样做:
int main()
{
Base b = { .Base_var2 = "main_var2" };
Derived1 d1 = { b,.derived1_var3 = "main_var3" };
Derived2 d2 = { d1,.derived2_var5 = "main_var5" };
std::cout <<
d2.Base_var1 << "," <<
d2.Base_var2 << "," <<
d2.derived1_var3 << "," <<
d2.derived2_var4 << "," <<
d2.derived2_var5 << std::endl;
}
这有几个缺陷。最重要的是它不是正确的 C++。当我们尝试用 clang++ 编译它时,我们得到:
>clang++ -std=c++20 troep.cc
troep.cc:21:22: warning: mixture of designated and non-designated initializers in the same initializer list is a C99 extension [-Wc99-designator]
Derived1 d1 = { b,.derived1_var3 = "main_var3" };
^~~~~~~~~~~~~~~~~~~~~~~~~~~~
troep.cc:21:19: note: first non-designated initializer is here
Derived1 d1 = { b,.derived1_var3 = "main_var3" };
^
troep.cc:22:23: warning: mixture of designated and non-designated initializers in the same initializer list is a C99 extension [-Wc99-designator]
Derived2 d2 = { d1,.derived2_var5 = "main_var5" };
^~~~~~~~~~~~~~~~~~~~~~~~~~~~
troep.cc:22:19: note: first non-designated initializer is here
Derived2 d2 = { d1,.derived2_var5 = "main_var5" };
^~
2 warnings generated.
程序的输出是:
Derived1_var1,main_var2,main_var3,Derived2_var4,main_var5
这是想要的结果。
使用 clang++ 您只会收到警告,但使用 g++ 时,它不会编译并出现错误:
>g++ -std=c++20 troep.cc
troep.cc: In function ‘int main()’:
troep.cc:21:22: error: either all initializer clauses should be designated or none of them should be
21 | Derived1 d1 = { b,.derived1_var3 = "main_var3" };
| ^
troep.cc:22:23: error: either all initializer clauses should be designated or none of them should be
22 | Derived2 d2 = { d1,.derived2_var5 = "main_var5" };
| ^
第二个问题是 Derived1
并没有真正覆盖 Base::Base_var1
的值,而是隐藏了它。如果将其传递给采用 Base&
的函数,则 Base_var1
将具有“意外”值。
我解决第一个问题的方法是将结构拆分为定义变量和(初始)默认值的*Ext
(扩展)结构,并使用没有成员变量的类进行继承。这样您就不会遇到混合使用指定和非指定的需要:
struct Base {
char const* Base_var1 = "Base_var1";
char const* Base_var2;
};
struct Derived1Ext {
char const* derived1_var3 = "Derived1_var3";
};
struct Derived2Ext {
char const* derived2_var4 = "Derived2_var4";
char const* derived2_var5 = "Derived2_var5";
};
class Derived1 : public Base,public Derived1Ext {
};
class Derived2 : public Base,public Derived1Ext,public Derived2Ext {
};
Derived2
的初始化然后变成:
Derived2 d2 = {
{ .Base_var2 = "main_var2" },{ .derived1_var3 = "main_var3" },{ .derived2_var5 = "main_var5" }
};
它有额外的大括号,但总体来说相当干净——而且你仍然只需要指定你想要与默认值不同的值。
当然,这仍然缺少对 Base_var1
的默认值的覆盖。
但是我们可以编译它而没有错误或警告;)。然后输出是
Base_var1,main_var5
最后一步是修复此处的第一个值,无需再次更改 main()
。
我能想到的唯一方法是使用魔法值...在上面的例子中,我们只有 char const*
但总的来说这可能会变得相当复杂。然而,这里有一些东西可以解决问题:
#include <iostream>
#include <cassert>
char const* const use_default = reinterpret_cast<char const*>(0x8); // Magic value.
struct BaseExt {
char const* Base_var1 = use_default;
char const* Base_var2 = use_default;
};
struct Base : BaseExt {
void apply_defaults()
{
if (Base_var1 == use_default)
Base_var1 = "Base_var1";
// Always initialize Base_var2 yourself.
assert(Base_var2 != use_default);
}
};
struct Derived1Ext {
char const* derived1_var3 = use_default;
};
struct Derived1 : BaseExt,Derived1Ext {
void apply_defaults()
{
if (Base_var1 == use_default)
Base_var1 = "Derived1_var1"; // Override default of Base!
if (derived1_var3 == use_default)
derived1_var3 = "Derived1_var3";
BaseExt* self = this;
static_cast<Base*>(self)->apply_defaults();
}
};
struct Derived2Ext {
char const* derived2_var4 = use_default;
char const* derived2_var5 = use_default;
};
struct Derived2 : BaseExt,Derived1Ext,Derived2Ext {
void apply_defaults()
{
if (derived2_var4 == use_default)
derived2_var4 = "Derived2_var4";
if (derived2_var5 == use_default)
derived2_var5 = "Derived2_var5";
BaseExt* self = this;
static_cast<Derived1*>(self)->apply_defaults();
}
};
int main()
{
Derived2 d2 = {
{ .Base_var2 = "main_var2"},{ .derived2_var5 = "main_var5" },};
d2.apply_defaults();
std::cout <<
d2.Base_var1 << "," <<
d2.derived2_var5 << std::endl;
}
编辑: 改进版
您可以使用 use_default
代替 std::optional
幻数。并且不必手动调用 apply_defaults
,您可以添加其他成员 (dummy
) 为您执行此操作。通过添加 [[no_unique_address]]
,这种添加(可能)完全被优化掉了(结构的大小不会增加(当然,使用 std::optional
确实会使结构增加))。
#include <iostream>
#include <optional>
#include <cassert>
//------------------------------------------------------------------------
// Base
struct BaseExt {
std::optional<char const*> Base_var1 = std::optional<char const*>{};
std::optional<char const*> Base_var2 = std::optional<char const*>{};
};
struct Base;
struct BaseApply { BaseApply(Base&); };
struct Base : BaseExt {
void apply_defaults()
{
if (!Base_var1)
Base_var1 = "Base_var1";
// Always initialize Base_var2 yourself.
assert(Base_var2);
}
[[no_unique_address]] BaseApply dummy{*this};
};
BaseApply::BaseApply(Base& base) { base.apply_defaults(); }
//------------------------------------------------------------------------
// Derived1
struct Derived1Ext {
std::optional<char const*> derived1_var3 = std::optional<char const*>{};
};
struct Derived1;
struct Derived1Apply { Derived1Apply(Derived1&); };
struct Derived1 : BaseExt,Derived1Ext {
void apply_defaults()
{
if (!Base_var1)
Base_var1 = "Derived1_var1"; // Override default of Base!
if (!derived1_var3)
derived1_var3 = "Derived1_var3";
BaseExt* self = this;
static_cast<Base*>(self)->apply_defaults();
}
[[no_unique_address]] Derived1Apply dummy{*this};
};
Derived1Apply::Derived1Apply(Derived1& derived1) { derived1.apply_defaults(); }
//------------------------------------------------------------------------
// Derived2
struct Derived2Ext {
std::optional<char const*> derived2_var4 = std::optional<char const*>{};
std::optional<char const*> derived2_var5 = std::optional<char const*>{};
};
struct Derived2;
struct Derived2Apply { Derived2Apply(Derived2&); };
struct Derived2 : BaseExt,Derived2Ext {
void apply_defaults()
{
if (!derived2_var4)
derived2_var4 = "Derived2_var4";
if (!derived2_var5)
derived2_var5 = "Derived2_var5";
BaseExt* self = this;
static_cast<Derived1*>(self)->apply_defaults();
}
[[no_unique_address]] Derived2Apply dummy{*this};
};
Derived2Apply::Derived2Apply(Derived2& derived2) { derived2.apply_defaults(); }
//------------------------------------------------------------------------
int main()
{
Derived2 d2 = {
{ .Base_var2 = "main_var2"},};
std::cout <<
*d2.Base_var1 << "," <<
*d2.Base_var2 << "," <<
*d2.derived1_var3 << "," <<
*d2.derived2_var4 << "," <<
*d2.derived2_var5 << std::endl;
}
这打印为输出:
Derived1_var1,main_var5
^^^^^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^ ^^^^^^^^^
Base_var1,Base_var2 derived1_var3 derived2_var5
default by derived2_var
Derived1. default by
Derived2.