C++中是否有像C#中的new声明一样的声明?

4
我想知道是否有类似于C#中的new声明可以用于C++。
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) 这个列表会变得无穷无尽... 而我已经在我的类中有了这些属性... 所以我想知道是否可以这样做..


1
除了统一初始化之外,您还应该了解 std::make_unique。在现代 C++ 代码中,new 很少使用。 - Jesse Good
太好了,但是我当前的应用程序中没有可用的C++ 14。不过,我想知道C++ 14是否提供了我所请求的功能 :) - Jimmyt1988
1
@Jimmyt1988:如果您无法使用 C++14,可以把这个作为工具添加到您的项目中;)。 - Jesse Good
你应该考虑到必须要“删除”分配的内存,所以如果foo保留了指针并且之后可以被删除或者删除它,那么这是一个很好的用法,但如果没有,就会导致内存泄漏。 - SHR
2
如果你不能使用C++14,为什么要标记你的问题为C++14? - kfsone
显示剩余6条评论
4个回答

6

您可以使用Lambda:

FuncCall( []{ Foo f; f.Bar = "sausage"; f.Boo = 4; return f; }() );

实时例子


Foo没有构造函数参数。 - Jimmyt1988
这真是太棒了!我不必将那些垃圾移动到函数内了... 嗯嗯,好吃好吃。C++中的Lamdba似乎在这里拯救了一下,尽管它们在上面被声明,但我讨厌将所有这些所有权转移过去... 很酷... 有很多改进空间,但现在我会将您标记为答案。 - Jimmyt1988
我理解一个聪明的编译器(例如:gcc,clang等)会消除lambda调用并在编译时将其内联吗? - Cole Tobin
@ColeJohnson 我希望如此,但我还没有检查/测量。编译器没有理由不能这样做,但我怀疑没有人会给你保证 :) - Daniel Frey

2

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});
}

我个人并没有做任何与你在这里所做的不同,但我只是想评论一下,是否您可能无意中在推广关于C++中struct的神话...? - Lightness Races in Orbit
这很有帮助!非常感谢。我猜我得坚持定义一个巨大的参数列表来初始化所有参数...哦,多么令人愉快啊。 - Jimmyt1988
1
@LightnessRacesinOrbit 或许吧。那么为了读者清楚明白:structclass 除了成员和基类的默认可访问性不同外,其余完全相同:struct 默认为 public。我只是根据最小化显式可访问性说明符的数量来选择两者之间的区别。一些编码标准规定在特定用例中应使用哪种类型,例如“对于聚合类型,请使用struct,否则请使用class”。 - bames53
@Jimmyt1988:为什么你的类“非常非常非常”庞大? - Lightness Races in Orbit
@Jimmyt1988 在C++中,通常用户不需要了解太多有关初始化对象的知识:类应该负责此事,并允许用户传递最多几个参数来配置初始化。如果该类只是客户端代码应该控制设置的数据块(在C中很常见),则使用聚合类型。 - bames53

0
请注意以下内容:
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指令 - 您会发现它执行同样冗余的操作(在更复杂的情况下,可能还涉及装箱/拆箱)。
当您合并不可复制或不可移动类型时,您的C++方法也会失败,这将迫使您传递指针和/或弯曲设计。
看起来您正在尝试重新创建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 ---

"也许应该重新考虑你的设计" ... 大型可选参数列表的一个问题是增长。你可能会得到相互依赖、相互矛盾或相互作用的参数。你可以选择不验证这些参数,或者最终得到复杂的构造函数。

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 ...
        }
        ...
    }
};

一种避免这种情况的方法是使用封装/聚合相关成员。
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=*/{}, ...};

如果您随后决定停止使用构造函数并转而使用设置器,这将翻译得相当好,因为您可以有重载的“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;
};

重新考虑你的设计,我不确定这是否必要...尤其是当所有这些参数都与 Foo 的构造相关联时(或者在我的真实类中,它是一个 Window)...我有高度、宽度、标题、类名、handletowindow...但我想分享一下你的意见也是好的。 - Jimmyt1988
“should probably”。“重新思考”在字面上意思并非“重新开始”,而是指用稍有不同的方式得出类似的设计。我曾与多个项目一起使用“大结构,按需填充”的方法,在项目扩展时会遇到许多问题,例如冲突设置和/或互相依赖的值(如果没有设置“boo”,那么“baz”就没有任何意义;如果已经设定了“parent”,则必须指定“modality”)。此时,最好把默认构造函数设为普通,使用设置器来处理所有东西。或者使用聚合参数。 - kfsone
该死,我甚至没有看到关于伪代码的评论,你用那个令人惊叹的语法让我兴奋了...哈哈...可恶。 - Jimmyt1988

-3

您可以使用统一初始化来进行初始化。


此外,这并没有回答问题。他不是在问如何使用{},而是在问如何使用构造函数的可选参数。 - kfsone

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接