是否有像 C# 中那样的 C++ new 声明
Is there a C++ new declaration like in C#
我想知道是否有像 C# for C++ 中那样的 new
声明
C# 允许您执行此操作,它只是稍微整理了代码:
FuncCall( new Foo() {
Bar = "sausage",
Boo = 4
} );
只是我觉得这在 C++ 中有点草率:
unique_ptr<Foo> foo( new Foo() );
foo.Bar = "sausage";
foo.Boo = 4;
FuncCall( move( foo ) );
Foo 可能看起来像这样:
class Foo
{
public:
Foo();
string Bar;
int Boo;
}
为什么我不把所有都放入构造参数中?
因为当你必须初始化那么多的时候是愚蠢的:
Foo( int width, int height, string title, string className, string thjis, stihjrjoifger gfirejgoirejgioerjgoire )
它永远持续下去......而我已经在我的 class 中拥有这些属性......所以只是想知道是否可以完成......
可以使用uniform initialization来初始化
您可能会使用 lambda:
FuncCall( []{ Foo f; f.Bar = "sausage"; f.Boo = 4; return f; }() );
Foo
必须有一个构造函数或者是一个聚合类型:
class Foo {
std::string bar;
int boo;
public:
Foo(std::string s, int i) : bar(std::move(s)), boo(i) {}
};
void FuncCall(std::unique_ptr<Foo> ptr) {}
int main() {
FuncCall(make_unique<Foo>("sausage", 4));
}
struct Foo {
std::string bar;
int boo;
};
void FuncCall(std::unique_ptr<Foo> ptr) {}
int main() {
std::unique_ptr<Foo> foo(new Foo{"sausage", 4});
FuncCall(foo);
}
或者你可以避免指针:
void FuncCall(Foo foo) {}
int main() {
FuncCall({sausage", 4});
}
注意以下几点:
class Foo {
std::string m_bar = "Bar";
int m_baz = 3;
float m_boo = 4.2;
/* ... */
public:
Foo() {} // or Foo() = default or elision
};
int main() {
Foo f;
f.m_bar = "wunderBar";
}
展开如下:
Foo* fptr = stack_allocate<Foo*>(sizeof(Foo));
// from ctor
fptr->m_bar.string("Bar"); // construct m_bar
fptr->m_baz.int(3);
fptr->m_boo.float(4.2);
// your code:
fptr->m_bar.operator=("wunderBar");
出于类似的原因,您可能想查看 C# 构造的 IL 指令 - 您会发现它执行的操作同样冗余(在更复杂的情况下,可能 boxing/unboxing)。
当您合并不可复制或不可移动的类型时,您的 C++ 方法也会失败,这将迫使您传递指针 and/or 扭曲您的设计。
您/似乎/正在尝试做的是重新创建 Python 的可选参数:
# C++-alike
class Foo(object):
def __init__(self, Bar, Baz, Boo):
...
# C#-alike:
class Foo(object):
def __init__(self, Bar="Bar", Baz=13, Boo=4.2):
...
C++ 没有提供这样做的直接方法,最接近的机制是默认参数和运算符重载:
class Foo {
std::string m_bar = "Bar";
int m_baz = 3;
float m_boo = 4.2;
public:
Foo(std::string bar="Bar", int baz=3, int boo=6.1)
: m_bar(bar), m_baz(baz), m_boo(boo)
{}
/* Foo* f = new Foo(); => new Foo(bar="Bar", baz=13, boo=6.1);
* Foo* f = new Foo("hello"); => new Foo(bar="hello", baz=3, boo=4.2);
* Foo* f = new Foo("hello", 1, 1.); => new Foo(bar="hello", baz=1, boo=1.);
* Foo* f = new Foo(42.); => invalid, arguments must be in order.
*/
};
或
class Foo {
std::string m_bar = "Bar";
int m_baz = 3;
float m_boo = 4.2;
public:
Foo() = default;
// allow Foo("hello")
Foo(const char* bar) : m_bar(bar) {}
Foo(const std::string& bar) : m_bar(bar) {}
Foo(std::string&& bar) : m_bar(std::forward(bar)) {}
// allow Foo(10, 12.)
explicit Foo(int baz, float boo) : m_baz(baz), m_boo(boo) {}
/* Foo* f = new Foo(); => new Foo(bar="Bar", baz=3, boo=4.2);
* Foo* f = new Foo("hello"); => new Foo(bar="hello", baz=3, boo=4.2);
* Foo* f = new Foo(1, 1.); => new Foo(bar="Bar", baz=1, boo=1.);
* Foo* f = new Foo(42.); => invalid, no match
*/
};
请参阅 http://ideone.com/yFIqlA 了解 SSCE。
如果您真的有十几种不同的构造函数配置,您可能应该重新考虑您的设计。
--- 编辑 ---
注意:在构造函数中公开所有参数不是强制性的:
class Foo {
std::string m_user_supplied;
std::time_t m_time;
public:
Foo() : m_user_supplied(), m_time(0) {}
Foo(std::string src) : m_user_supplied(src), m_time(0) {}
void addTime(time_t inc) { m_time += inc; }
};
--- 编辑 2 ---
"should maybe rethink your design" ... 大型可选参数列表的一个问题是增长。您很可能会得到相互依赖、相互矛盾或相互作用的参数。您可以选择不验证这些,或者最终得到复杂的构造函数。
struct Foo {
...
FILE* m_output;
const char* m_mode;
...
Foo(..., FILE* output, const char* mode, ...)
{
...
if (output != nullptr) {
ASSERT( output == nullptr || mode != nullptr );
... other requirements
} else {
if (mode != nullptr)
... might not be an error but it might be a bug ...
}
...
}
};
避免这种情况的一种方法是使用 encapsulation/aggregation 相关成员。
class Foo {
...
struct FileAccess {
FILE* m_file;
const char* m_mode;
constexpr FileAccess() : m_file(nullptr), m_mode(nullptr) noexcept {}
FileAccess(FILE* file, const char* mode) : m_file(file), m_mode(mode) {
if (file == nullptr || mode == nullptr)
throw invalid_argument("file/mode cannot be null");
}
};
...
FileAccess m_access;
Foo(..., FileAccess access, ...);
};
这可以公平地减少膨胀。如果你的 API 是稳定的,你可以将它与初始化列表一起使用(如果你的 API 不稳定并且你做改变会咬你的屁股)
auto fooWithFile = make_unique<Foo>{..., /*access=*/{stdout, "w"}, ...};
auto fooWithout = make_unique<Foo>{..., /*access=*/{}, ...};
如果您随后决定停止使用 ctors 并转而使用 setter,这将转换得相当好,因为您可以重载 "set",它采用各种配置结构之一:
auto foo = make_unique<Foo>();
foo->set(FileAccess(stdout, "w"))
->set(Position(Right, -100, Top, 180))
->set(DimensionPercentage(80, 75));
对
auto foo = make_unique<Foo>() { # pseudo based on if C++ had the C# syntax
m_file = stdout;
m_mode = "w";
m_xPosition = -100;
m_xPositionRel = Right;
m_yPosition = -180;
m_yPositionRel = Top;
m_dimensionType = Percentage;
m_xDimension = 80;
m_yDimension = 75;
};
我想知道是否有像 C# for C++ 中那样的 new
声明
C# 允许您执行此操作,它只是稍微整理了代码:
FuncCall( new Foo() {
Bar = "sausage",
Boo = 4
} );
只是我觉得这在 C++ 中有点草率:
unique_ptr<Foo> foo( new Foo() );
foo.Bar = "sausage";
foo.Boo = 4;
FuncCall( move( foo ) );
Foo 可能看起来像这样:
class Foo
{
public:
Foo();
string Bar;
int Boo;
}
为什么我不把所有都放入构造参数中?
因为当你必须初始化那么多的时候是愚蠢的:
Foo( int width, int height, string title, string className, string thjis, stihjrjoifger gfirejgoirejgioerjgoire )
它永远持续下去......而我已经在我的 class 中拥有这些属性......所以只是想知道是否可以完成......
可以使用uniform initialization来初始化
您可能会使用 lambda:
FuncCall( []{ Foo f; f.Bar = "sausage"; f.Boo = 4; return f; }() );
Foo
必须有一个构造函数或者是一个聚合类型:
class Foo {
std::string bar;
int boo;
public:
Foo(std::string s, int i) : bar(std::move(s)), boo(i) {}
};
void FuncCall(std::unique_ptr<Foo> ptr) {}
int main() {
FuncCall(make_unique<Foo>("sausage", 4));
}
struct Foo {
std::string bar;
int boo;
};
void FuncCall(std::unique_ptr<Foo> ptr) {}
int main() {
std::unique_ptr<Foo> foo(new Foo{"sausage", 4});
FuncCall(foo);
}
或者你可以避免指针:
void FuncCall(Foo foo) {}
int main() {
FuncCall({sausage", 4});
}
注意以下几点:
class Foo {
std::string m_bar = "Bar";
int m_baz = 3;
float m_boo = 4.2;
/* ... */
public:
Foo() {} // or Foo() = default or elision
};
int main() {
Foo f;
f.m_bar = "wunderBar";
}
展开如下:
Foo* fptr = stack_allocate<Foo*>(sizeof(Foo));
// from ctor
fptr->m_bar.string("Bar"); // construct m_bar
fptr->m_baz.int(3);
fptr->m_boo.float(4.2);
// your code:
fptr->m_bar.operator=("wunderBar");
出于类似的原因,您可能想查看 C# 构造的 IL 指令 - 您会发现它执行的操作同样冗余(在更复杂的情况下,可能 boxing/unboxing)。
当您合并不可复制或不可移动的类型时,您的 C++ 方法也会失败,这将迫使您传递指针 and/or 扭曲您的设计。
您/似乎/正在尝试做的是重新创建 Python 的可选参数:
# C++-alike
class Foo(object):
def __init__(self, Bar, Baz, Boo):
...
# C#-alike:
class Foo(object):
def __init__(self, Bar="Bar", Baz=13, Boo=4.2):
...
C++ 没有提供这样做的直接方法,最接近的机制是默认参数和运算符重载:
class Foo {
std::string m_bar = "Bar";
int m_baz = 3;
float m_boo = 4.2;
public:
Foo(std::string bar="Bar", int baz=3, int boo=6.1)
: m_bar(bar), m_baz(baz), m_boo(boo)
{}
/* Foo* f = new Foo(); => new Foo(bar="Bar", baz=13, boo=6.1);
* Foo* f = new Foo("hello"); => new Foo(bar="hello", baz=3, boo=4.2);
* Foo* f = new Foo("hello", 1, 1.); => new Foo(bar="hello", baz=1, boo=1.);
* Foo* f = new Foo(42.); => invalid, arguments must be in order.
*/
};
或
class Foo {
std::string m_bar = "Bar";
int m_baz = 3;
float m_boo = 4.2;
public:
Foo() = default;
// allow Foo("hello")
Foo(const char* bar) : m_bar(bar) {}
Foo(const std::string& bar) : m_bar(bar) {}
Foo(std::string&& bar) : m_bar(std::forward(bar)) {}
// allow Foo(10, 12.)
explicit Foo(int baz, float boo) : m_baz(baz), m_boo(boo) {}
/* Foo* f = new Foo(); => new Foo(bar="Bar", baz=3, boo=4.2);
* Foo* f = new Foo("hello"); => new Foo(bar="hello", baz=3, boo=4.2);
* Foo* f = new Foo(1, 1.); => new Foo(bar="Bar", baz=1, boo=1.);
* Foo* f = new Foo(42.); => invalid, no match
*/
};
请参阅 http://ideone.com/yFIqlA 了解 SSCE。
如果您真的有十几种不同的构造函数配置,您可能应该重新考虑您的设计。
--- 编辑 ---
注意:在构造函数中公开所有参数不是强制性的:
class Foo {
std::string m_user_supplied;
std::time_t m_time;
public:
Foo() : m_user_supplied(), m_time(0) {}
Foo(std::string src) : m_user_supplied(src), m_time(0) {}
void addTime(time_t inc) { m_time += inc; }
};
--- 编辑 2 ---
"should maybe rethink your design" ... 大型可选参数列表的一个问题是增长。您很可能会得到相互依赖、相互矛盾或相互作用的参数。您可以选择不验证这些,或者最终得到复杂的构造函数。
struct Foo {
...
FILE* m_output;
const char* m_mode;
...
Foo(..., FILE* output, const char* mode, ...)
{
...
if (output != nullptr) {
ASSERT( output == nullptr || mode != nullptr );
... other requirements
} else {
if (mode != nullptr)
... might not be an error but it might be a bug ...
}
...
}
};
避免这种情况的一种方法是使用 encapsulation/aggregation 相关成员。
class Foo {
...
struct FileAccess {
FILE* m_file;
const char* m_mode;
constexpr FileAccess() : m_file(nullptr), m_mode(nullptr) noexcept {}
FileAccess(FILE* file, const char* mode) : m_file(file), m_mode(mode) {
if (file == nullptr || mode == nullptr)
throw invalid_argument("file/mode cannot be null");
}
};
...
FileAccess m_access;
Foo(..., FileAccess access, ...);
};
这可以公平地减少膨胀。如果你的 API 是稳定的,你可以将它与初始化列表一起使用(如果你的 API 不稳定并且你做改变会咬你的屁股)
auto fooWithFile = make_unique<Foo>{..., /*access=*/{stdout, "w"}, ...};
auto fooWithout = make_unique<Foo>{..., /*access=*/{}, ...};
如果您随后决定停止使用 ctors 并转而使用 setter,这将转换得相当好,因为您可以重载 "set",它采用各种配置结构之一:
auto foo = make_unique<Foo>();
foo->set(FileAccess(stdout, "w"))
->set(Position(Right, -100, Top, 180))
->set(DimensionPercentage(80, 75));
对
auto foo = make_unique<Foo>() { # pseudo based on if C++ had the C# syntax
m_file = stdout;
m_mode = "w";
m_xPosition = -100;
m_xPositionRel = Right;
m_yPosition = -180;
m_yPositionRel = Top;
m_dimensionType = Percentage;
m_xDimension = 80;
m_yDimension = 75;
};