【问题标题】:Can an object know its own constness?一个对象可以知道它自己的常量吗?
【发布时间】:2013-03-07 19:00:03
【问题描述】:

使用decltypestd::is_const 可以从外部检测变量的常量性。 但是对象也有可能知道自己的常量吗?用法应该是这样的:

#include <type_traits>
#include <iostream>
#include <ios>

struct Test
{
    Test() {}

    bool print() const
    {
       // does not work as is explained in https://stackoverflow.com/q/9890218/819272
       return std::is_const<decltype(*this)>::value; // <--- what will work??
    }
};

int main()
{
    Test t;
    const Test s;

    // external constness test
    std::cout << std::boolalpha << std::is_const<decltype(t)>::value << "\n";
    std::cout << std::boolalpha << std::is_const<decltype(s)>::value << "\n";

    // internal constness test
    std::cout << std::boolalpha << t.print() << "\n";
    std::cout << std::boolalpha << s.print() << "\n"; // <--- false??
}

LiveWorkSpace 上的输出这可能吗?

MOTIVATION:我希望能够检测 const 成员函数是在 const 对象上调用还是来自非常量对象。该对象可以例如表示缓存,成员表示视图。如果缓存是 const 的,可能会使用优化的绘制例程,而如果底层数据是非常量的,则绘制例程需要定期检查数据是否被刷新。

注意:相关的 question 询问如何破坏 const 对象的构建,但我不太明白这个答案是否意味着我的问题的明确否定。如果没有,我想在布尔值中捕获常量以供进一步使用。

编辑:正如@DanielFrey 指出的那样,构造函数不是测试常量的好地方。 const 成员函数呢?


更新:感谢大家纠正我最初提出的问题并提供答案的各个部分(构造函数定义不明确的常量,this 的右值,@987654327 的上下文含义@,事后看来,我忽略了明显的重载技巧,以及潜伏在阴影中的 const 引用别名漏洞)。对我来说,这个问题是最好的 Stackoverflow。我决定选择 @JonathanWakely 的答案,因为它展示了如何定义 MutableImmutable 类来加强 constness 概念,从而以万无一失的方式实现我想要的。

【问题讨论】:

  • 这似乎很哲学。
  • @Richard 因此标签为“反思”(因为缺乏“冥想”)
  • @rhalbersma 我看到 C++ 标记与反射混合在一起,想知道你为什么在一个问题中混合 C++ 和 C#!

标签: c++ reflection c++11 typetraits const-correctness


【解决方案1】:

构造函数(原始问题)不可能,因为

12.1 构造函数 [class.ctor]

4 构造函数不得为virtual (10.3) 或static (9.4)。可以为 constvolatileconst volatile 对象调用构造函数。构造函数不得声明为constvolatileconst volatile (9.3.2)。 constvolatile 语义 (7.1.6.1) 不适用于正在构建的对象。它们在派生最多的对象 (1.8) 的构造函数结束时生效。构造函数不得使用 ref-qualifier 声明。

对于成员函数(当前问题),您可以简单地同时提供const 和非const 重载,将两者转发到将常量作为布尔模板参数的(私有)方法。

【讨论】:

  • +1 很好的答案,所以我改写了 const 成员函数的问题(对此感到抱歉)。
  • 哈!我们酝酿了相同的解决方法。称赞您令人印象深刻的解决方法。 :)
  • @DrewDormann:除了你忘记将常量作为编译时值转发给实现;)
  • @rhalbersma:在非常量重载中,调用private_fun&lt;false&gt;(...),在常量中调用private_fun&lt;true&gt;(...)。请注意,private_fun 需要声明为 const 才能从 const 公共函数内部调用。不过,如果布尔值是true,你可以使用const_cast
  • 调用哪个成员函数取决于调用表达式——例如p-&gt;function()中指针的类型,与实际对象的常量无关。
【解决方案2】:

正如其他人所说,您无法判断一个对象是否在成员函数中被声明为const。您只能判断它是否在 const 上下文中被调用,这是不一样的。

动机:我希望能够检测 const 成员函数是在 const 对象上调用还是来自非常量对象。该对象可以例如表示缓存,成员表示视图。如果缓存是 const 的,可能会使用优化的绘制例程,而如果底层数据是非常量的,则绘制例程需要定期检查数据是否被刷新。

你无法可靠地判断这一点。

struct A
{
  void draw() { fut = std::async(&A::do_draw, this, false); }
  void draw() const { fut = std::async(&A::do_draw, this, true); }
  void update(Data&);
private:
  void do_draw(bool this_is_const) const;
  mutable std::future<void> fut;
};

A a;
const A& ca = a;
ca.draw();           // object will think it's const, but isn't
Data new_data = ...;
a.update(new_data);  // do_draw() should recheck data, but won't

您可以通过定义单独的可变和不可变类型在类型系统中对其进行建模。

struct Base
{
  virtual ~Base();
  virtual void draw() const = 0;
protected:
  void do_draw(bool) const;
};

struct MutableCache : Base
{
  virtual void draw() const { fut = std::async(&Base::do_draw, this, false); }
  void update();
};

struct ImmutableCache : Base
{
  virtual void draw() const { fut = std::async(&Base::do_draw, this, true); }
  // no update function defined, data cannot change!
};

现在,如果将缓存创建为ImmutableCache,您就知道它不能更改,从而取代了您之前对“const”对象的想法。 MutableCache 可以更改,因此需要检查刷新的数据。

【讨论】:

  • 我不认为do_draw 不是const 有效,因为你不能用Base const* 调用它,这就是你在draw 中所拥有的。
  • @Xeo,哎呀,我在第一个例子中做对了,但在第二个例子中变得懒惰:)
  • +1 示例。在第一个示例中,我了解对非 const 对象进行 const 引用会调用 const 成员,而draw() 可能会显示过时的视图(@Xeo 在他的回答中也指出了这一点)。但是a.draw()为什么不重新检查数据呢?
  • 啊,好吧,我看错了:ca.draw() 不会重新检查通过a.update() 更改的数据。
  • 是的,关键是在成员函数中,您无法判断对象是否真的 const。但是对于不允许修改的类型,您可以确定它不会改变
【解决方案3】:

什么会起作用??

什么都行不通。

运行其构造函数的对象是从不const。它只能分配给const 变量构造函数运行之后。

对于成员函数也无法确定(包括非常量成员函数,因为可能已经使用了const_cast

常量是存在于每个调用点的属性,而不是函数体本身的属性。

动机:我希望能够检测是否在 const 对象上调用了 const 成员函数

你不能这样做,但你可以接近......

class C
{
public:
    void func() const
    {
        std::cout << "const!";
        func_impl();
    }
    void func()
    {
        std::cout << "non-const!";
        func_impl();
    }
private:
    void func_impl() const;
};

对象可以例如表示缓存,成员表示视图。如果缓存是 const 的,可能会使用优化的绘制例程,而如果底层数据是非常量的,则绘制例程需要定期检查数据是否被刷新。

这将是 const 的不可靠用法,因为 const 不是对象本身的属性。它是正在使用的对象的当前上下文的一个属性。

在当前上下文中检测到它是 const 并不能告诉您该对象一直在 const 上下文中处理。

【讨论】:

  • +1 但如果测试发生在成员函数内部怎么办?我会更新问题。
  • 如果构造函数是constexpr呢?
  • @CrazyEddie:什么都不做,只是告诉编译器对象构造可以作为常量表达式的一部分发生。
【解决方案4】:

this(以及 *this 的类型)的类型完全由函数的 cv 限定符决定,不会根据实际对象是否为 cv 限定而改变。

§9.3.2 [class.this] p1

在非静态 (9.3) 成员函数的主体中,关键字this 是一个prvalue 表达式,其值是调用该函数的对象的地址。 this 在类X 的成员函数中的类型是X*如果成员函数声明为const,则this的类型为const X*,如果成员函数声明为volatile,则this的类型为volatile X*,并且如果成员函数声明为const volatile,则this的类型为const volatile X*

所以,你看不到成员函数的inside,它被调用的对象是否是const,但你可以让编译器根据constness 调用不同的函数:

struct X{
  void f(){ /* non-const object */ }
  void f() const{ /* const object */ }
};

int main(){
  X x1;
  X const x2;
  x1.f(); // calls non-const 'f'
  x2.f(); // calls const 'f'

  // but beware:
  X const& rx = x1;
  rx.f(); // calls const 'f', because the "object" it's invoked on is const
}

不过,请注意 sn-p 中列出的限制。

【讨论】:

  • +1 表示标准报价和示例。我还需要不同的返回类型(begin()iteratorconst_iterator 一样),但这也可以。
【解决方案5】:

我不知道是否有可能以这种方式,通过在构造函数中设置的成员值,但对象可以通过成员函数报告其 const-ness:

struct Test
{
  bool is_const() const
  {
    return(true);
  }

  bool is_const()
  {
    return(false);
  }
};

【讨论】:

  • +1 愚蠢地忽略了这一点,因为 STL 曾经为 begin()/end() 迭代器执行此操作(而现在他们已经转到 cbegin()/cend()
  • @rhalbersma:他们没有“去”,他们只是添加那些,因为专门从非const 对象(有some helpers you can use,让它更好)。
  • 请注意,t.is_const() 并不意味着更改 t 将是明确定义或未定义的,因为强制转换意味着人们可以随意更改 constness。
  • @GManNickG 好点,但我更担心墨菲而不是马基雅维利(Herb Sutter 之后免费)
【解决方案6】:

这是不可能的,因为特定值可能同时被视为const 而不是const。考虑

MyType t = ...;
MyType& nonConstRef = t;
const MyType& constRef = t;

此时t 有一个const 和一个非常量引用。

【讨论】:

    【解决方案7】:

    检测对象内的 constness 是不可能的,但您声明您的动机是……

    “我希望能够检测 const 成员函数是在 const 对象上调用还是来自非 const 对象。”

    这很简单,只需提供非const 重载即可。

    重载可以遵循一个常见的实现,例如如下:

    #include <type_traits>
    #include <iostream>
    using namespace std;
    
    class Test
    {
    private:
        template< class CvTest >
        static void print( CvTest& o )
        {
            cout << boolalpha;
            cout << "> object says: Hey, const = " << std::is_const<CvTest>::value << "!" << endl;
        }
    
    public:
        bool print()        { return (print( *this ), false); }
        bool print() const  { return (print( *this ), true); }
    };
    
    int main()
    {
        Test t;
        const Test s;
    
        cout << "External constness test:" << endl;
        cout << boolalpha << is_const<decltype(t)>::value << "\n";
        cout << boolalpha << is_const<decltype(s)>::value << "\n";
    
        cout << endl;
    
        cout << "Internal constness test:" << endl;
        cout << boolalpha << t.print() << "\n";
        cout << boolalpha << s.print() << "\n";
    }
    

    结果:

    外部常数测试: 错误的 真的 内部常数测试: > 对象说:嘿,const = false! 错误的 > 对象说:嘿,const = true! 真的

    【讨论】:

      猜你喜欢
      • 2011-09-25
      • 2011-12-30
      • 2012-06-11
      • 2012-09-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-09-14
      • 1970-01-01
      相关资源
      最近更新 更多