【问题标题】:Must I use pointers for my C++ class fields?我必须为我的 C++ 类字段使用指针吗?
【发布时间】:2009-04-10 13:10:51
【问题描述】:

在阅读了关于the difference between pointers and references 的问题后,我决定对我的类字段使用引用而不是指针。然而,这似乎是不可能的,因为它们不能被声明为未初始化(对吗?)。

在我现在正在处理的特定场景中,我不想使用普通变量(顺便说一下,它们的正确术语是什么?)因为它们在我声明它们时会自动初始化。

在我的 sn-p 中,bar1 使用默认构造函数自动实例化(这不是我想要的), &bar2 导致编译器错误,因为您不能使用未初始化的引用(正确吗?),并且 *bar3 很高兴因为可以声明指针未初始化(顺便说一句,将其设置为 NULL 是最佳实践吗?)。

class Foo
{
public:
    Bar bar1;
    Bar &bar2;
    Bar *bar3;
}

看起来我必须在这种情况下使用指针,这是真的吗?另外,使用变量的最佳方法是什么? -> 语法有点麻烦……运气不好? smart pointers 等呢?这相关吗?

更新 1:

尝试在我的类中实现引用变量字段并在构造函数中对其进行初始化后,为什么会收到以下错误?

../src/textures/VTexture.cpp: In constructor ‘vimrid::textures::VTexture::VTexture()’:
../src/textures/VTexture.cpp:19: error: uninitialized reference member ‘vimrid::textures::VTexture::image’

这是真正的代码:

// VTexture.h
class VTexture
{
public:
    VTexture(vimrid::imaging::ImageMatrix &rImage);
private:
    vimrid::imaging::ImageMatrix ℑ
}

// VTexture.cpp
VTexture::VTexture(ImageMatrix &rImage)
    : image(rImage)
{
}

我也尝试在标题中这样做,但没有运气(我得到同样的错误)。

// VTexture.h
class VTexture
{
public:
    VTexture(vimrid::imaging::ImageMatrix &rimage) : image(rImage) { }
}

更新 2:

弗雷德·拉森 - 是的!有一个默认构造函数;我忽略了它,因为我认为它与问题无关(我多么愚蠢)。删除默认构造函数后,我导致编译器错误,因为该类与 std::vector 一起使用,它需要有默认构造函数。所以看起来我必须使用默认构造函数,因此必须使用指针。惭愧……是吗? :)

【问题讨论】:

  • “普通变量(顺便说一句,它们的正确术语是什么?)”您可以称它们为“堆栈变量”
  • "普通变量" --> "原始数据类型"
  • 原始数据类型不是唯一的普通变量...您也可以将用户定义的数据类型作为普通变量。
  • 嗯,当我说“普通变量”时,我的意思是“不是指针也不是引用”,所以我将 Foo::bar1 称为“普通变量” - 这是什么你们在说什么?
  • 你称它为成员、指针成员或引用成员。

标签: c++ pointers reference


【解决方案1】:

问题 1 的答案:

但是这似乎是不可能的,因为它们 [references] 不能被声明为未初始化(对吗?)。

没错。


问题 2 的答案:

在我的 sn-p 中,bar1 是自动 用默认实例化 构造函数(这不是我想要的), &bar2 导致编译器错误,因为 你不能使用未初始化的引用 (对吗?),

您在构造函数的初始化列表中初始化您的类的引用:

class Foo
{
public:
    Foo(Bar &rBar) : bar2(rBar), bar3(NULL)
    {
    }

    Bar bar1;
    Bar &bar2;
    Bar *bar3;
}

问题 3 的答案:

在我正在工作的特定场景中 现在,我不想使用 正常变量(什么是正确的 顺便说一下他们的术语?)

它们没有正确的名称,通常您可以为大多数讨论(除了这个)说出指针,并且您需要讨论的所有内容也适用于引用。您可以通过初始化器列表以相同的方式初始化非指针、非引用成员。

class Foo
{
public: 
  Foo()  : x(0), y(4)
  {
  }

  int x, y;
};

问题 4 的答案:

指针可以声明为未初始化 (顺便说一句,最好的做法是 将此设置为 NULL?)。

它们可以被声明为未初始化是的。最好将它们初始化为 NULL,因为这样您就可以检查它们是否有效。

int *p = NULL;
//...

//Later in code
if(p)
{
  //Do something with p
}

问题 5 的答案:

看来我必须使用指针 在这种情况下,这是真的吗?还, 什么是使用的最佳方式 变量?

您可以使用指针或引用,但不能重新分配引用并且引用不能为 NULL。指针就像任何其他变量一样,就像 int 一样,但它拥有一个内存地址。数组是另一个变量的别名。

指针有自己的内存地址,而数组应该被视为共享它引用的变量的地址。

有了引用,在它被初始化和声明后,你可以像使用它引用的变量一样使用它。没有特殊的语法。

使用指针,要访问它保存的地址处的值,您必须dereference 指针。你可以通过在它前面放一个 * 来做到这一点。

int x=0;
int *p = &x;//p holds the address of x
int &r(x);//r is a reference to x
//From this point *p == r == x
*p = 3;//change x to 3
r = 4;//change x to 4
//Up until now
int y=0;
p = &y;//p now holds the address of y instead.

问题 6 的答案:

智能指针等呢?是 这相关吗?

使用智能指针(参见 boost::shared_ptr),以便在堆上分配时无需手动释放内存。我上面给出的例子都没有分配在堆上。这是一个使用智能指针会有所帮助的示例。

void createANewFooAndCallOneOfItsMethods(Bar &bar)
{
    Foo *p = new Foo(bar);  
    p->f();
    //The memory for p is never freed here, but if you would have used a smart pointer then it would have been freed here.  
}

问题 7 的答案:

更新 1:

在尝试实施一个 我的类中的引用变量字段 并在 构造函数,为什么我会收到 以下错误?

问题是您没有指定初始化列表。请参阅我对上述问题 2 的回答。冒号后的所有内容:

class VTexture
{
public:
    VTexture(vimrid::imaging::ImageMatrix &rImage)
      : image(rImage)
    { 
    }
private:
    vimrid::imaging::ImageMatrix ℑ
}

【讨论】:

  • @Brian:感谢您的回答,我在使用成员初始化列表时遇到了问题,请您看看更新 #1 吗?
  • @Brian:你确定吗?我已经在 cpp 和 h 文件中显示了初始化列表的示例......无论如何,我添加了更新 2。如果我仍然在某个地方出错,请告诉我。
  • 我认为你可能有 2 个构造函数
【解决方案2】:

它们可以被初始化。你只需要使用成员初始化器列表。

Foo::Foo(...) : bar1(...), bar2(...), bar3(...)
{
  // Whatever
}

以这种方式初始化所有成员变量是个好主意。否则,对于原始类型以外的其他类型,C++ 无论如何都会使用默认构造函数对其进行初始化。在大括号内分配它们实际上是重新分配它们,而不是初始化它们。

另外,请记住,成员初始化器列表指定了如何初始化成员变量,而不是顺序。成员按照声明的顺序进行初始化,而不是按照初始化程序的顺序。

【讨论】:

  • 感谢您的回答,我在使用成员初始化器列表时遇到问题,请您看看更新 #1 吗?
  • 一些非原始类型默认是未初始化的。示例是非 POD 结构的 POD 对象和 POD 部分。对于所有未初始化的元素,大括号内的赋值等同于初始化。推荐的方法是初始化列表。最后一部分我会多加 1 分...
  • 从错误消息中,您似乎有一个默认构造函数没有初始化“图像”成员。
  • ... 这很有趣——如果你有一个默认的构造函数,你必须找到或创建一些对象来初始化那个引用。如果它不引用任何东西是有效的,它必须是一个指针。
  • @Fred Larson: 非常好,先生!我忽略了它,因为我认为它无关……具有讽刺意味。
【解决方案3】:

使用null object design pattern

我使用的是ints,但任何类型都一样。

//header file
class Foo
{
public:
   Foo( void );
   Foo( int& i );
private:
   int& m_int;
};

//source file
static int s_null_Foo_m_i;

Foo::Foo( void ) :
   m_i(s_null_Foo_m_i)
{ }

Foo::Foo( int& i ) :
   m_i(i)
{ }

现在您必须确保 Foo 在默认构造时有意义。您甚至可以检测 Foo 的默认构造时间。

bool Foo::default_constructed( void )
{
   return &m_i == &s_null_Foo_m_i;
}

我完全同意这种观点,总是更喜欢引用而不是指针。有两种值得注意的情况是您无法摆脱参考成员:

  1. Null 有一个有意义的值。

    这可以通过空对象设计模式来避免。

  2. 类必须是可分配的。

    编译器不会为具有引用成员的类生成赋值运算符。您可以自己定义一个,但您将无法更改引用的绑定位置。

【讨论】:

    【解决方案4】:

    当你定义 Bar 和 Bar * 时定义也会有副作用

    class Foo
    {
    public:
        Bar bar1; // Here, you create a dependency on the definition of Bar, so the header //file for bar always needs to be included.
        Bar &bar2;
        Bar *bar3; //Here, you create a pointer, and a forward declaration is enough, you don't have to always include the header files for Bar , which is preferred.
    }
    

    【讨论】:

    • 谢谢,那么我们可以对引用成员使用前向声明还是需要包含标题?我想是前者……
    【解决方案5】:

    仅仅因为 -> 语法繁琐而使用引用并不是最好的理由... 引用比指针有一个很大的优势,因为如果不使用转换技巧,空值是不可能的,但也有初始化的缺点和风险意外地非法绑定临时对象,然后超出范围(例如,在隐式转换之后)。

    是的,诸如 boost 之类的智能指针几乎总是处理复合成员的正确答案,偶尔也适用于关联成员 (shared_ptr)。

    【讨论】:

    • 呵呵,我知道有人会接受 -> 语法注释 - 感谢您的提示,非常有帮助。
    • IIRC,您只能将临时对象绑定到 const 引用,然后临时对象在引用期间一直存在。分配一个指向临时的指针,然后你就会遇到问题。这里的引用更安全。
    • @David:我犯了同样的错误。临时绑定到 const ref 的生命周期受代码块的限制。一旦退出代码块(例如返回一个绑定了 const ref 的对象),该引用就变得毫无意义。这就是为什么引用看起来比指针更安全,但并非总是如此。
    • 请参阅 Herb Sutter 的 GOTW #88,了解有关成员引用为何危险的更多详细信息。
    【解决方案6】:
    class Foo {
    public:
        Bar bar1;
        Bar &bar2;
        Bar *bar3;
    
        // member bar2 must have an initializer in the constructor
        Bar::Bar(Bar& _bar2) : bar1(), bar2(_bar2), bar3(new Bar()) {}
    
        Bar::~Bar() {delete bar3;}
    }
    

    请注意,bar2 不只是在 ctor 中初始化;它使用作为参考参数传入的bar 对象进行初始化。该对象和bar2 字段将在新Foo 对象的生命周期内绑定在一起。这通常是一个非常糟糕的主意,因为很难确保两个对象的生命周期能够很好地协调(即,您永远不会在处理 Foo 对象之前处理传入的 bar 对象。)

    这就是为什么最好使用实例变量(如bar1)或指向在堆上分配的对象的指针(如bar3)的原因。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-03-08
      • 2012-10-30
      • 2021-10-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多