常量和指针

3

编辑1:我意识到如果没有了解我想要做什么,很难理解这个问题。A类并不完整,但实质上代表了C数组的“代理”(或“查看器”或“采样器”)。其有趣的用法之一是将C数组呈现为2D网格(相关函数未在此处显示)。该类的属性如下:

  • 它不应该拥有数据-没有深拷贝
  • 它应该可以复制/分配
  • 它应该轻量级(
  • 它应该保持constness(我对此有困惑)

请不要质疑目的或设计:它们是问题的假设。

首先是一些代码:

class A
{
private:
    float* m_pt;
public:
    A(float* pt)
        :m_pt(pt)
    {}
    const float* get() const
    {
        return m_pt;
    }
    void set(float pt)
    {
        *m_pt = pt;
    }
};

void gfi()
{
    float value = 1.0f;
    const A ac(&value);
    std::cout<<(*ac.get())<<std::endl;
    A a = ac;
    a.set(2.0f);
    std::cout<<(*ac.get())<<std::endl;
}

调用“gfi”将生成以下输出:
1
2

a 赋值为 ac 是一种简单的方法来快速取消 ac 的常数性。有更好的方法来保护 m_pt 指向的值吗?
请注意,我确实希望我的类可以被复制/赋值,但我不希望在此过程中失去其常数性。
编辑0:我还希望在其中有一个指针,并且不需要深度复制(假设指针可以是一个巨大的数组)。
编辑2:由于答案的帮助,我得出结论“const构造函数”将是一个有用的东西(至少在这个上下文中)。我查了一下,当然我不是唯一一个得出这个结论的人。这里有一个有趣的讨论: http://www.rhinocerus.net/forum/language-c-moderated/569757-const-constructor.html 编辑3:最后得到了我满意的结果。感谢您的帮助。进一步的反馈将不胜感激。
template<typename T>
class proxy
{
public:
    typedef T elem_t;
    typedef typename boost::remove_const<T>::type elem_unconst_t;
    typedef typename boost::add_const<T>::type elem_const_t;
public:
    elem_t* m_data;
public:
    proxy(elem_t* data = 0)
        :m_data(data)
    {}
    operator proxy<elem_const_t>()
    {
        return proxy<elem_const_t>(m_data);
    }
}; // end of class proxy

void test()
{
    int i = 3;
    proxy<int> a(&i);
    proxy<int> b(&i);
    proxy<const int> ac(&i);
    proxy<const int> bc(&i);
    proxy<const int> cc = a;
    a=b;
    ac=bc;
    ac=a;
    //a=ac; // error C2679: binary '=' : no operator found which takes a right-hand operand of type...
    //ac.m_data[0]=2; // error C3892: 'ac' : you cannot assign to a variable that is const
    a.m_data[0]=2;
}
8个回答

2

你的类设计不太合理:

  • 应该使用浮点数值而不是指针
  • 如果想要使用指针,可能需要动态分配它们
  • 然后你需要给你的类添加一个复制构造函数、赋值运算符(以及析构函数),这将解决问题

或者,通过将复制构造函数和赋值运算符设为私有,并不实现它们来防止复制和赋值。


当你不知道一个类将被用于什么时,你怎么能说它设计得很糟糕呢? - anon
1
它无法用于你提出的使用案例。因此,它的设计很糟糕。 - anon

1

你可以使用代理模式和额外的运行时常量布尔成员来进行一些技巧操作。但首先,请告诉我们为什么这样做。


我在问题开头添加了进一步的解释。 - anon

1

实际上,您的类就像一个只能看到一个值的迭代器。它不封装数据,只是指向它。

您面临的问题已经在迭代器中得到了解决,您应该阅读一些有关创建自己的iteratorconst_iterator对的文档,以了解如何做到这一点。

注意:通常情况下,const iterator是一个不能增加/减少但可以更改其所指向的值的迭代器。而const_iterator是一个不同的类,可以增加/减少,但它所指向的值不能被更改。

这与const float *float *const之间的区别相同。在您的情况下,Afloat *相同,const Afloat *const相同。

对我来说,您的选择似乎是:

  • 封装您的数据。
  • 像迭代器一样创建一个单独的const_A类
  • 创建自己的复制构造函数,不允许复制const A,例如使用A(A & a);签名

这个答案与MadKeithV的答案类似。我强烈怀疑它最终会成为正确的答案,但我仍然希望有一个更简单的答案... - anon

0

编辑:考虑了一下这个问题,我认为您对成员指针的const正确性的影响有误解。请看下面这个令人惊讶的例子:

//--------------------------------------------------------------------------------
class CNotSoConstPointer
 {
 float *mp_value;

 public:
   CNotSoConstPointer(float *ip_value) : mp_value(ip_value) {}

   void ModifyWithConst(float i_value) const
     {
     mp_value[0] = i_value;
     }

   float GetValue() const
     {
     return mp_value[0];
     }
 };

//--------------------------------------------------------------------------------
int _tmain(int argc, _TCHAR* argv[])
  {
  float value = 12;
  const CNotSoConstPointer b(&value);
  std::cout << b.GetValue() << std::endl;

  b.ModifyWithConst(15);
  std::cout << b.GetValue() << std::endl;

  while(!_kbhit()) {}
  return 0;
  }

这将输出12,然后是15,而不会对const不那么const的对象的const正确性进行“聪明”的处理。原因是只有指针本身是const,而不是它所指向的内存。

如果您想要后者,您需要更多的包装来获得所需的行为,就像我下面的原始建议或Iain的建议。

原始答案:


你可以为你的数组代理创建一个模板,专门针对const数组进行const版本的特化。这个特化版本将有一个const *m_pt,返回一个const指针,在尝试设置时抛出错误等等。
编辑:类似于这样:
template<typename T>
class TProxy
  {
  T m_value;

  public:
    TProxy(T i_t) : m_value(i_t) {};

    template<typename T>
    TProxy(const TProxy<T> &i_rhs) : m_value(i_rhs.m_value) {}

    T get() { return m_value; }
    void set(T i_t) { m_value = i_t; }
  };

template<typename T>
class TProxy<const T *>
  {
  const T *mp_value;

  public:
    TProxy(const T *ip_t) : mp_value(ip_t) {};

    template<typename T>
    TProxy(const TProxy<T> &i_rhs) : m_value(i_rhs.mp_value) {}

    T get() { return m_value; }    
  };

如果我找不到更简单的方法,那就是答案了。只要不对我的代码造成太多麻烦,我可以使用const。从我的角度来看,我会说你的答案有些勉强…… - anon
关于编辑,我理解并同意。 但是我认为我可能可以“滥用”const语义以达到另一个目的。实际上,当将函数声明为“const”时,我可以决定保护比传统方式更多的内容。 请注意,要实现这个目标唯一缺少的是“const构造函数”。基本上,无法声明构造函数仅能创建const对象。可惜... - anon
实际上,按照这种方式,你可以拥有一个私有构造函数和一个只创建常量对象的工厂方法。 - Joris Timmermans
赋值和复制构造函数仍然应该正常工作。我认为工厂只能提供指针解决方案(不能在堆栈上进行构造),并且语法繁琐。这将破坏轻量级(和易于使用)的特点。 - anon

0

您可以拒绝特定参数组合的复制构造函数:

例如,添加构造函数;

A(A& a) :m_pt(a.m_pt) { m_pt = a.m_pt; }

防止任何A的实例被初始化为const A

这也阻止了const A a2 = a1,其中a1是const的,但你应该从来不需要这样做,因为你可以直接使用a1 - 即使你可以复制,a2也将永远与a1相同。


这个有效。 不幸的是,我还需要允许: const A a2 = a1; 事实上,一个函数可能返回一个const类: const A getProxy() const; - anon

0
为什么不在A中将float*替换为float。如果你不这样做,那么float*引用的原始所有者可以更改它,或者任何准备对a::get的返回值进行可变转换的人都可以更改它。

那可能是一个答案,但我忘了说我实际上需要指针成为指针(请参见问题中的编辑)。 - anon

0

const总是只是一个提示编译器的方法;没有办法使变量永久只读。


0

我认为你应该使用深拷贝并定义自己的赋值运算符和复制构造函数。

此外,返回内部数据结构的句柄并不是一个好的做法。


没有深拷贝是一个答案,但在这里不可能(请参见编辑) - anon

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