【问题标题】:Can I make a variable _const from now on_?我可以从现在开始创建一个变量 _const 吗?
【发布时间】:2014-06-17 11:15:35
【问题描述】:

我正在使用一个库,该库有一个类,该类的 init 函数与其构造函数不同。每次创建新实例时,我都需要调用,例如:

MyClass a;
a.init();

由于 init 不是 const,这会阻止我创建 const 实例(我不能写 const MyClass a)。有什么方法可以调用 init 然后从“here on out”声明(我猜对于范围的其余部分)我的变量是const

这可行,但依赖于不触及原始变量:

MyClass dont_touch;
dont_touch.init();
const MyClass & a = dont_touch;

【问题讨论】:

  • 答案是否定的。像你一样,我希望是的。
  • 您也可以使用const_cast 调用init 来快速而肮脏地进行操作。也就是说,为init 调用声明对象const,并在对象周围声明const_cast。但是,const_cast 通常被认为是不好的做法,我不得不说我认为templated solution 非常漂亮。
  • @Apriori 绝对未定义的行为。不能 const_cast 从已定义的 const 对象中去掉 const 并对它做非常量的事情。
  • @Andre Kostur:谢谢,好电话,我从未意识到或忘记这是未定义的行为。从好的方面来说,你可以看到我使用了多少 const_cast。 C++ 给了我枪...
  • 如果你有一个对非 const 底层对象的 const 引用,你可以抛弃 const .. 但在这个例子中不是这样

标签: c++ constructor reference constants


【解决方案1】:

如果你使用 C++11,你可以使用 lambda 函数

const MyClass ConstantVal = []{ 
    MyClass a;
    a.init(); 
    return a; 
}();

这使您可以保持初始化,同时永远不会让外部访问可变对象。 也可以看看: http://herbsutter.com/2013/04/05/complex-initialization-for-a-const-variable/

【讨论】:

  • 我认为这也比我的解决方案更可优化,因为编译器可以假设对象永远不会被修改。
  • 这回答了我的具体问题。虽然我本可以问一个更笼统的问题,将 a.init() 替换为一些可能不适合 lambda 的任意操作。
【解决方案2】:

您可以创建一个包装类并使用它。

如果 MyClass 有一个虚拟析构函数,您可以像这样从它派生感到安全:

class WrapperClass : public MyClass 
{
public:
    WrapperClass()
    {
        init(); // Let's hope this function doesn't throw
    }
};

或者写一个包含MyClass实例的类

class WrapperClass
{
public:
    WrapperClass()
    {
        m_myClass.init(); // Let's hope this function doesn't throw
    }
    operator MyClass&() {return m_myClass;}
    operator const MyClass&() const {return m_myClass;}
private:
    MyClass m_myClass;
};

或者使用上述两种解决方案之一编写一个模板来解决这个一般问题:例如。

template <class T> class WrapperClass : public T
{
public:
    WrapperClass()
    {
        T::init();
    }
};

typedef WrapperClass<MyClass> WrapperClass;

【讨论】:

  • 可能值得注意的是,如果需要,可以在所使用的函数内部和本地定义这样的派生类。但是使用模板解决方案会更好,并且可以避开这一点,因为您可以随意实例化。
  • init 投掷完全没问题。希望它不会返回错误代码。 init函数的通常原因是开发者知道可能会发生错误,但不知道异常,所以如果发生在ctor中他无法报告错误。
【解决方案3】:

创建一个包含前两行的函数,并为您提供一个准备就绪的对象。

MyClass makeMyClass()
{
   MyClass a;
   a.init();
   return a;
}

// Now you can construct a const object or non-const object.
const MyClass a = makeMyClass();
MyClass b = makeMyClass(); 

更新

使用makeMyClass() 涉及在每次调用函数时构造和销毁临时对象。如果这成为一笔巨大的成本,makeMyClass() 可以更改为:

MyClass const& makeMyClass()
{
   static bool inited = false;
   static MyClass a;
   if ( !inited )
   {
     inited = true;
     a.init();
   }
   return a;
}

如前所述,它的用法将继续有效。另外,once也可以这样做:

const MyClass& c = makeMyClass();

【讨论】:

  • 是的,它确实是以复制为代价的。
  • @mangledorf 我不确定,但return value optimization 可能有用。因此,如果我理解正确,如果您使用-O3,我认为您无需担心这一点。只是一个想法。
  • 即使存在 RVO,您也需要确保类的复制构造函数是正确的。
  • const MyClass a = makeMyClass(); 也可能是const MyClass&amp; a = makeMyClass();
  • 如果类启用了移动(使用 c++11),那么保证不会复制。
【解决方案4】:

即使没有 C++11 和 lambda,您实际上也可以很简单地做到这一点:

const MyClass a;
{
    MyClass _a;
    _a.init();
    std::swap(const_cast<MyClass&>(a), _a);
}

const_cast 的使用诚然是一种 hack,但它不会破坏任何东西,因为 const 是一个相当弱的说明符。同时,它非常高效,因为MyClass 对象只是交换,而不是复制(最合理的复制成本高的对象应该提供swap 函数并注入std::swap 的重载)。

没有演员表,它需要一个助手:

struct Construct_Init {
    operator MyClass() const
    {
        MyClass a;
        a.init();
        return a;
    }
};
const MyClass a = Construct_Init();

这可以在函数中是这样的(Construct_Init 结构不需要在命名空间范围内声明),但它有点长。对象的副本可能会或可能不会使用复制省略进行优化。

请注意,在这两种情况下,init() 的返回值都会丢失。如果它返回一个布尔值,其中true 是成功,false 是失败,最好:

if(!a.init())
    throw std::runtime_error("MyClass init failed");

或者只是确保正确处理错误。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-05
    • 2011-06-21
    • 2015-03-27
    • 1970-01-01
    相关资源
    最近更新 更多