【问题标题】:What's a good way of declaring instance variables in C++ without constructing them? [closed]在 C++ 中声明实例变量而不构造它们的好方法是什么? [关闭]
【发布时间】:2012-10-05 21:45:53
【问题描述】:

我一直在互联网上搜索这个主题,但我还没有真正找到一个确定的答案。作为主要的 C# 程序员,我习惯于在大范围内声明类,通常在文件顶部附近,在任何函数之外,然后在使用时构造它们。

在转向 C++ 之后,复制它的唯一方法是拥有一个默认构造函数,这很好,但在某些情况下,我宁愿拥有一个需要参数的构造函数,而不是一个无参数的默认构造函数。

在互联网上搜索解决方案后,我遇到了一些建议,它们有缺陷:

1.指针

有人建议在想要的范围内有一个动态指针,然后在构造的时候将指针指向类的位置。

CClass* pClass = 0;

int main()
{
    pClass = new CClass(1337);

    delete pClass;
    return 0;
}

这种方法的问题是你必须记住在之后删除指针,因此,静态指针更“安全”。另外,我猜这样做会产生轻微的内存开销,尽管不会太多,因为有指针。

2。无论如何都有一个默认构造

有时建议使用默认构造函数,它将类中的所有内容归零:

class CClass
{
public:
    CClass() : leetNumber(0) {}
    CClass(int leetNumber) : leetNumber(leetNumber) {}
private:
    int leetNumber;
};

//Defaults leetNumber to 0 through default ctor
CClass myClass;

int main()
{
    myClass = CClass(1337);

    return 0;
}

但是,如果您不能将课堂内的所有内容都归零,会发生什么?如果你有另一个类不能被初始化为空怎么办?如果用户试图在没有正确初始化成员的情况下访问类中的函数,你会怎么做? (你可以检查一下,但我相信这需要太多代码,尤其是如果你有很多成员)。

3.停留在更小、更本地化的范围内

有人建议人们说要保持在小范围内,将类作为引用传递给可能需要它的其他函数,并在声明类后立即构造:

class CClass
{
public:
    CClass(int leetNumber) : leetNumber(leetNumber) {}
    int getLeetNumber() { return leetNumber; }
private:
    int leetNumber;
};

bool GetMuchNeededAmazingNumberFromClass(CClass& myClass)
{
    if(myClass.getLeetNumber() == 1337)
        return true;

    return false;
}

int main()
{
    CClass myClass = CClass(1337);
    if(!GetMuchNeededAmazingNumberFromClass(&myClass);
        return 1;

    return 0;
}

从某种意义上说,这很好,您可以看到什么函数需要什么,但我可以想象一个函数需要大量具有大量所需参数的外部类。

还有更多示例,但我似乎找不到可以依赖的示例,尤其是来自 C# 背景的示例,这些示例既好用又简单。

谢谢。

编辑:

让我详细说明我的要求 - 在 C# 中,您可以执行以下操作:

public class Program
{
    //See how I'm able to do this, without calling the ctor.
    static AmazingClass amazing;

    public static void Main()
    {
        //And then call the constructor when I want.
        amazing = new AmazingClass(1337);
    }
}

这允许我创建类而不实际构造它,这就是我在 C++ 中寻找的。​​p>

再次感谢。

【问题讨论】:

  • 我认为您的意思是“声明实例变量而不构造它们”。声明类意味着别的东西。
  • 我不确定您要解决的实际外部问题是什么。为什么一个典型的函数需要的成员不止是极少数的类?您通常不需要告诉该函数哪些它应该操作这些类的成员吗?你真的需要多久构造一个类的实例来执行一个操作?
  • 我真的不确定这里的问题是什么。
  • 您应该研究智能指针。有几种类型,它们会在您完成后删除您的对象。
  • 在(好的)C++ 中,我们不再直接使用new,请参阅make_uniquemake_shared。编辑:根据您的编辑,您要寻找的只是动态内存管理。 C# 隐含地(在一定程度上)对类类型执行此操作。

标签: c++ class scope


【解决方案1】:

听起来你想重载构造函数。

【讨论】:

    【解决方案2】:

    在函数中使用静态变量将对象的创建延迟到需要时。

    Foo & getInstance () {
        static Foo foo(arg1, arg2, ...);
        return foo;
    }
    
    void main () {
        Foo & x = getInstance();
    }
    

    如果你需要getInstance创建一个动态对象(如getInstance(x, y, z)),但只想传入一次参数,你可以这样做:

    struct FooFactory {
        int arg1;
        float arg2;
        Bar arg3;
    
        bool valid;
    
        FooFactory ();
    
        Foo & getInstance ();
    };
    
    FooFactory::FooFactory () : valid(false) {}
    
    Foo & FooFactory::getInstance () {
        if (!valid) throw Error();
        static Foo foo(arg1, arg2, arg3);
        return foo;
    }
    
    FooFactor factory;
    
    void main () {
        factory.arg1 = ...;
        factory.arg2 = ...;
        factory.arg3 = ...;
        factory.valid = true;
        Foo & x = factory.getInstance();
    }
    

    当然这是基础。我不在乎信息隐藏或类似的事情。您可以通过使用operator() () 而不是getInstance () 并将factory 重命名为getInstance 来避免factory.getInstance()。我也不是说这是个好主意。我只是在展示如何从字面上完成 OP 的问题。

    【讨论】:

    • 我非常喜欢这种方法,但最好的做法是放弃广泛范围的想法,而是像 PiotrNycz 的回答那样坚持使用本地方法,或者我应该坚持我的意图并使用像这样的东西?谢谢。
    【解决方案3】:

    这是个很坏的习惯(用对象代替类):

    我习惯于在一个大的 范围,通常在文件顶部附近,在任何函数之外,以及 然后在使用时构建它们。

    忘记它。在需要时定义对象。

    int main() { 
         A a;
         ...a...
         A b;
         ...b...
     }
    

    这是 C++ 的想法。

    我相信 C# 也是一个坏习惯。如果你使用对象而不定义它怎么办——你会得到空引用异常——为什么要玩这么危险的东西。

    顺便说一句,C++ 等价于 C# 对象是 shared_ptr:

    std::shared_ptr<A> a;
    int main() {
       a = std::make_shared<A>(...);
       // do not need to call delete
    }
    

    在 C++ 中,如果您不需要共享对象,也可以使用 std::unique_ptr

    但是不要这样做,不要使用全局变量...

    【讨论】:

    • 如果一个函数(无论出于何种原因)需要很多类,我该怎么办?我还应该通过参数传递它们吗?
    • @seandewar5:隐藏函数访问那些其他对象的事实并没有使代码更简单,它只会让查找依赖项和维护变得更加模糊
    • @seandewar5 - 您正在使用全局变量进行编程。这在上世纪 70 年代被认为是坏习惯。
    • @seandewar5 我认为你的术语搞混了。你不传递类 - 你传递对象。
    • 我会将此标记为我的答案,因为我相信这是更好的做法,虽然不完全是我的全部意图,但我相信它更好地适应。谢谢。 :)
    【解决方案4】:
    1. 停留在更小、更本地化的范围内

    这是您应该做的,不仅在 C++ 中,而且在所有语言中都应尽可能多地使用。

    但我可以想象一个需要大量外部类的函数,这些类具有大量所需的参数。

    函数应该接受他们需要接受的参数。如果您觉得太多,也许您应该将该功能重构为其他部分。参数的数量只是衡量函数复杂性的一种方式。访问全局对象不仅不会简化函数,还会使识别全局对象的使用/访问/修改位置变得更加困难,进而使您的代码更难维护。

    如果你的函数有很多参数,要么它不是一个真正的函数,而是不同操作的复杂混乱,要么参数可能组合成一些有意义的实体。在后一种情况下,只需创建代表这些实体的类型,您最终会传递一些参数。

    顺便说一句,我不确定在 C# 中你是否真的按照你所说的去做......特别是,C# 中的大多数代码都在类中,所以很可能你习惯做的是拥有类成员在顶部声明并在其他任何地方使用。如果是这种情况,您可以在 C++ 中应用相同的范例。创建类,包含您需要的成员数量。

    【讨论】:

    • 感谢您,我一定会使用尽可能本地的范围。 +1
    【解决方案5】:

    这是因为在 C# 中,类是基于堆的引用对象。所以:

    C#

    MyClass a; //null reference
    a = new MyClass (param1, param2);
    

    但是:

    C++

    MyClass a; //Myclass is constructed on stack
    

    C# 版本相当于 C++ 中的这个:

    Myclass* a = 0; //null reference
    a = new Myclass (param1, param2);
    

    在 C++ 中,类可以存在于堆栈中,而在 C# 中则不能。

    C# 提供了一种可以存在于栈中的结构值类型:

    MyStruct a; //lives on the stack
    

    但是我可以提供一个带参数的结构构造函数:

    struct MyStruct
    {
       public MyStruct (int a, int b) { /*assign to members vars*/ }
    
       int A, B;
    }
    

    C#代码:

    MyStruct a; // this creates a on stack and zero initializes int A and B
    

    这个:

    MyStruct a; //does same as above
    
    a = new Mystruct(1, 2); //a.A = 1, a.B = 2
    

    【讨论】:

    • 哇,我从未注意到其中的相似之处,但我想我会坚持使用本地范围的方法,但无论如何都要 +1,因为要注意。 :)
    【解决方案6】:
    //See how I'm able to do this, without calling the ctor.
    static AmazingClass amazing;
    
    //And then call the constructor when I want.
    amazing = new AmazingClass(1337);
    

    一切(嗯,几乎一切)都是 C# 中的引用。该变量amazing 不会引用任何内容,直到您通过new 分配它。这是 C++ 的等价物:

    //See how I'm able to do this, without calling the ctor.
    static AmazingClass * amazing;
    
    //And then call the constructor when I want.
    amazing = new AmazingClass(1337);
    

    因为(几乎)所有内容都是 C# 和 Java 中的引用,所以您必须 new 这个、new 那、new 这些语言中的所有内容。通过将所有内容都设为指针,您可以在 C++ 中获得相同的行为,但这样做不是首选机制。 C++ 没有自动垃圾收集。您在 C++ 中通过new 分配的所有内容最终都必须通过delete 删除。 C++ 中的首选机制是将newdelete 保持在最低限度。

    在 C++ 中解决 new / delete 问题的一种方法是绕过 new。只需声明所需类型的变量。这为您提供了一些您在 Java 和 C# 中无法做到的事情。您可以声明一个类型的变量,但 Java 和 C# 不允许您看到对象本身。这些语言中的对象总是隐藏在引用后面。

    解决 C++ 中 new / delete 问题的另一种方法是使用 RAII。 newdelete 命令隐藏在类方法中,析构函数总是在自己之后清理。班级做肮脏的工作。对外界来说,你只需要使用这个类。

    C# 和 C++ 是不同的语言。您必须采用不同的思维方式才能正确使用两种语言。

    【讨论】:

    • "只需声明所需类型的变量。这为您提供了在 Java 和 C# 中无法做到的事情。您可以声明类型的变量,但 Java 和 C# 不允许您这样做 [原文如此] 查看对象本身。那些语言中的对象总是隐藏在引用后面。 - 这不是真的,C# 有引用类型和值类型。值类型在没有 new 运算符的情况下被实例化。并且值类型直接处理..想想int,float等,它们不是引用类型,它们不会隐藏在引用后面。
    • @GavinWilliams -- C# 中的值类型仅限于原始类型,例如 int 和 float,以及仅包含原始类型的结构。手头的问题是关于课程的。在 C# 中,您不能在不使用 new 的情况下声明类类型的变量。你可以在 C++ 中做到这一点。 C# 和 C++ 是不同的语言。问“我如何在语言 Y 中做 X,就像我在语言 Z 中做的一样”,一般来说,这是一个糟糕的问题。不同的语言有不同的做事方式。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-02-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多