C++带有延迟初始化的 const getter 方法

发布于 2024-09-12 22:18:39 字数 451 浏览 5 评论 0原文

为延迟初始化的成员变量实现 getter 方法并保持 const 正确性的正确方法是什么?也就是说,我希望我的 getter 方法是 const,因为在第一次使用它之后,它就是一个普通的 getter 方法。只有第一次(当对象第一次初始化时)const 不适用。我想做的事:

class MyClass {
  MyClass() : expensive_object_(NULL) {}
  QObject* GetExpensiveObject() const {
    if (!expensive_object_) {
      expensive_object = CreateExpensiveObject();
    }
    return expensive_object_;
  }
private:
  QObject *expensive_object_;
};

我可以一边吃蛋糕一边吃吗?

What is the proper way to implement a getter method for a lazily-initialized member variable and maintain const-correctness? That is, I would like to have my getter method be const, because after the first time it is used, it's a normal getter method. It is only the first time (when the object is first initialized) that const does not apply. What I would like to do:

class MyClass {
  MyClass() : expensive_object_(NULL) {}
  QObject* GetExpensiveObject() const {
    if (!expensive_object_) {
      expensive_object = CreateExpensiveObject();
    }
    return expensive_object_;
  }
private:
  QObject *expensive_object_;
};

Can I eat my cake and have it too?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

不可一世的女人 2024-09-19 22:18:39

这很好,并且是典型的做法。

您必须将 exppressive_object_ 声明为 mutable

mutable QObject *expensive_object_; 

mutable 基本上意味着“我知道我在一个 const 对象中,但修改它不会破坏常量。”

That's fine and is the typical way of doing it.

You will have to declare expensive_object_ as mutable

mutable QObject *expensive_object_; 

mutable basically means "I know I'm in a const object, but modifying this won't break const-ness."

π浅易 2024-09-19 22:18:39

我建议将 James Curran 的答案封装成如果您经常这样做,则可以使用它自己的类:

template <typename T>
class suspension{
   std::tr1::function<T()> initializer;
   mutable T value;
   mutable bool initialized;
public:
   suspension(std::tr1::function<T()> init):
      initializer(init),initialized(false){}
   operator T const &() const{
      return get();
   }
   T const & get() const{
      if (!initialized){
         value=initializer();
         initialized=true;
      }
      return value;
   }
};

现在在您的代码中使用它,如下所示:

class MyClass {
  MyClass() : expensive_object_(CreateExpensiveObject) {}
  QObject* GetExpensiveObject() const {
    return expensive_object_.get();
  }
private:
  suspension<QObject *> expensive_object_;
};

I propose encapsulating James Curran's answer into a class of its own if you do this frequently:

template <typename T>
class suspension{
   std::tr1::function<T()> initializer;
   mutable T value;
   mutable bool initialized;
public:
   suspension(std::tr1::function<T()> init):
      initializer(init),initialized(false){}
   operator T const &() const{
      return get();
   }
   T const & get() const{
      if (!initialized){
         value=initializer();
         initialized=true;
      }
      return value;
   }
};

Now use this in your code as follows:

class MyClass {
  MyClass() : expensive_object_(CreateExpensiveObject) {}
  QObject* GetExpensiveObject() const {
    return expensive_object_.get();
  }
private:
  suspension<QObject *> expensive_object_;
};
帅气尐潴 2024-09-19 22:18:39

使 expense_object_ 可变。

Make expensive_object_ mutable.

难理解 2024-09-19 22:18:39

使用 const_cast 在该特定位置回避 const。

QObject* GetExpensiveObject() const {
  if (!expensive_object_) {
    const_cast<QObject *>(expensive_object_) = CreateExpensiveObject();
  }
  return expensive_object_;
}

恕我直言,这比使 expense_object_ mutable 更好,因为您不会在所有其他方法中失去常量安全性。

Use a const_cast to side-step const in that one specific place.

QObject* GetExpensiveObject() const {
  if (!expensive_object_) {
    const_cast<QObject *>(expensive_object_) = CreateExpensiveObject();
  }
  return expensive_object_;
}

IMHO, this is better than making expensive_object_ mutable because you don't lose the const-safety in all your other methods.

冰魂雪魄 2024-09-19 22:18:39

您考虑过包装类吗?您也许可以使用智能指针之类的东西,仅使用 const 返回版本的 operator*operator-> 以及也许 operator[]...您可以从中获得类似 scoped_ptr 的行为作为奖励。

让我们试一下,我相信人们可以指出一些缺陷:

template <typename T>
class deferred_create_ptr : boost::noncopyable {
private:
    mutable T * m_pThing;
    inline void createThingIfNeeded() const { if ( !m_pThing ) m_pThing = new T; }
public:
    inline deferred_create_ptr() : m_pThing( NULL ) {}
    inline ~deferred_create_ptr() { delete m_pThing; }

    inline T * get() const { createThingIfNeeded(); return m_pThing; }

    inline T & operator*() const { return *get(); }
    inline T * operator->() const { return get(); }

    // is this a good idea?  unintended conversions?
    inline T * operator T *() const { return get(); }
};

使用 type_traits 可能会让这个更好......

您需要不同版本的数组指针,并且您可能必须如果您想将参数传递给 T 的构造函数,请使用创建者函子或工厂对象或其他东西。

但你可以像这样使用它:

class MyClass {
public:
    // don't need a constructor anymore, it comes up NULL automatically
    QObject * getExpensiveObject() const { return expensive_object_; }

protected:
    deferred_create_ptr<QObject> expensive_object_;
};

是时候去编译它,看看我是否可以打破它......=)

Have you considered a wrapper class? You might be able to get away with something like a smart pointer, with only const-returning versions of operator* and operator-> and maybe operator[]... You can get scoped_ptr-like behavior out of it as a bonus.

Let's give this a shot, I'm sure people can point out a few flaws:

template <typename T>
class deferred_create_ptr : boost::noncopyable {
private:
    mutable T * m_pThing;
    inline void createThingIfNeeded() const { if ( !m_pThing ) m_pThing = new T; }
public:
    inline deferred_create_ptr() : m_pThing( NULL ) {}
    inline ~deferred_create_ptr() { delete m_pThing; }

    inline T * get() const { createThingIfNeeded(); return m_pThing; }

    inline T & operator*() const { return *get(); }
    inline T * operator->() const { return get(); }

    // is this a good idea?  unintended conversions?
    inline T * operator T *() const { return get(); }
};

Use of type_traits might make this better...

You'd need different versions for array pointers, and you might have to play around a bit with a creator functor or factory object or something if you wanted to pass in arguments to T's constructor.

But you could use it like this:

class MyClass {
public:
    // don't need a constructor anymore, it comes up NULL automatically
    QObject * getExpensiveObject() const { return expensive_object_; }

protected:
    deferred_create_ptr<QObject> expensive_object_;
};

Time to go off and compile this and see if I can break it... =)

泼猴你往哪里跑 2024-09-19 22:18:39

提出一个更奇特的解决方案此处,但它不处理没有默认构造函数的类型...

Proposing an even fancier solution here, but it doesn't handle types with no default constructor...

零度℉ 2024-09-19 22:18:39

我创建了一个具有以下功能的类模板 Lazy

  • 与标准智能指针相似的熟悉接口
  • 支持没有默认构造函数的类型
  • 支持没有复制构造函数的(可移动)类型
  • 线程安全
  • 可使用引用语义复制:所有副本共享相同的状态;它们的价值仅被创造一次。

下面是如何使用它:

// Constructor takes function
Lazy<Expensive> lazy([] { return Expensive(42); });

// Multiple ways to access value
Expensive& a = *lazy;
Expensive& b = lazy.value();
auto c = lazy->member;

// Check if initialized
if (lazy) { /* ... */ }

这是实现。

#pragma once
#include <memory>
#include <mutex>

// Class template for lazy initialization.
// Copies use reference semantics.
template<typename T>
class Lazy {
    // Shared state between copies
    struct State {
        std::function<T()> createValue;
        std::once_flag initialized;
        std::unique_ptr<T> value;
    };

public:
    using value_type = T;

    Lazy() = default;

    explicit Lazy(std::function<T()> createValue) {
        state->createValue = createValue;
    }

    explicit operator bool() const {
        return static_cast<bool>(state->value);
    }

    T& value() {
        init();
        return *state->value;
    }

    const T& value() const {
        init();
        return *state->value;
    }

    T* operator->() {
        return &value();
    }

    const T* operator->() const {
        return &value();
    }

    T& operator*() {
        return value();
    }

    const T& operator*() const {
        return value();
    }

private:
    void init() const {
        std::call_once(state->initialized, [&] { state->value = std::make_unique<T>(state->createValue()); });
    }

    std::shared_ptr<State> state = std::make_shared<State>();
};

I've crated a class template Lazy<T> with the following features:

  • Familiar interface similar to standard smart pointers
  • Supports types without default constructor
  • Supports (movable) types without copy constructor
  • Thread-safe
  • Copyable using reference semantics: All copies share the same state; their value is created only once.

Here's how you use it:

// Constructor takes function
Lazy<Expensive> lazy([] { return Expensive(42); });

// Multiple ways to access value
Expensive& a = *lazy;
Expensive& b = lazy.value();
auto c = lazy->member;

// Check if initialized
if (lazy) { /* ... */ }

Here's the implementation.

#pragma once
#include <memory>
#include <mutex>

// Class template for lazy initialization.
// Copies use reference semantics.
template<typename T>
class Lazy {
    // Shared state between copies
    struct State {
        std::function<T()> createValue;
        std::once_flag initialized;
        std::unique_ptr<T> value;
    };

public:
    using value_type = T;

    Lazy() = default;

    explicit Lazy(std::function<T()> createValue) {
        state->createValue = createValue;
    }

    explicit operator bool() const {
        return static_cast<bool>(state->value);
    }

    T& value() {
        init();
        return *state->value;
    }

    const T& value() const {
        init();
        return *state->value;
    }

    T* operator->() {
        return &value();
    }

    const T* operator->() const {
        return &value();
    }

    T& operator*() {
        return value();
    }

    const T& operator*() const {
        return value();
    }

private:
    void init() const {
        std::call_once(state->initialized, [&] { state->value = std::make_unique<T>(state->createValue()); });
    }

    std::shared_ptr<State> state = std::make_shared<State>();
};
等风也等你 2024-09-19 22:18:39

我对这个主题进行了一些研究,并提出了一个替代解决方案,以防您使用 C++11。考虑以下内容:

class MyClass 
{
public:
    MyClass() : 
        expensiveObjectLazyAccess() 
    {
        // Set initial behavior to initialize the expensive object when called.
        expensiveObjectLazyAccess = [this]()
        {
            // Consider wrapping result in a shared_ptr if this is the owner of the expensive object.
            auto result = std::shared_ptr<ExpensiveType>(CreateExpensiveObject());

            // Maintain a local copy of the captured variable. 
            auto self = this;

            // overwrite itself to a function which just returns the already initialized expensive object
            // Note that all the captures of the lambda will be invalidated after this point, accessing them 
            // would result in undefined behavior. If the captured variables are needed after this they can be 
            // copied to local variable beforehand (i.e. self).
            expensiveObjectLazyAccess = [result]() { return result.get(); };

            // Initialization is done, call self again. I'm calling self->GetExpensiveObject() just to
            // illustrate that it's safe to call method on local copy of this. Using this->GetExpensiveObject()
            // would be undefined behavior since the reassignment above destroys the lambda captured 
            // variables. Alternatively I could just use:
            // return result.get();
            return self->GetExpensiveObject();
        };
    }

    ExpensiveType* GetExpensiveObject() const 
    {
        // Forward call to member function
        return expensiveObjectLazyAccess();
    }
private:
    // hold a function returning the value instead of the value itself
    std::function<ExpensiveType*()> expensiveObjectLazyAccess;
};

主要思想是保存一个返回昂贵对象作为成员而不是对象本身的函数。在构造函数中使用执行以下操作的函数进行初始化:

  • 初始化昂贵的对象
  • 用捕获已初始化对象并返回它的函数替换自身。
  • 返回对象。

我喜欢这一点的是,初始化代码仍然写在构造函数中(如果不需要惰性,我自然会将其放在构造函数中),即使它仅在第一次查询昂贵对象时才会执行。

这种方法的缺点是 std::function 在执行过程中重新分配自身。重新分配后访问任何非静态成员(在使用 lambda 的情况下捕获)将导致未定义的行为,因此需要额外注意。这也是一种黑客行为,因为 GetExpectiveObject() 是 const,但它仍然会在第一次调用时修改成员属性。

在生产代码中,我可能更愿意使成员可变为 詹姆斯·柯兰描述。这样,类的公共 API 就清楚地表明该成员不被视为对象状态的一部分,因此它不会影响常量性。

经过一番思考,我发现 std::async 与 std::launch::deferred 也可以与 std::shared_future 结合使用,以便能够多次检索结果。这是代码:

class MyClass
{
public:
    MyClass() :
        deferredObj()
    {
        deferredObj = std::async(std::launch::deferred, []()
        {
            return std::shared_ptr<ExpensiveType>(CreateExpensiveObject());
        });
    }

    const ExpensiveType* GetExpensiveObject() const
    {
        return deferredObj.get().get();
    }
private:
    std::shared_future<std::shared_ptr<ExpensiveType>> deferredObj;
};

I played a bit with this topic and came up with an alternative solution in case you use C++11. Consider the following:

class MyClass 
{
public:
    MyClass() : 
        expensiveObjectLazyAccess() 
    {
        // Set initial behavior to initialize the expensive object when called.
        expensiveObjectLazyAccess = [this]()
        {
            // Consider wrapping result in a shared_ptr if this is the owner of the expensive object.
            auto result = std::shared_ptr<ExpensiveType>(CreateExpensiveObject());

            // Maintain a local copy of the captured variable. 
            auto self = this;

            // overwrite itself to a function which just returns the already initialized expensive object
            // Note that all the captures of the lambda will be invalidated after this point, accessing them 
            // would result in undefined behavior. If the captured variables are needed after this they can be 
            // copied to local variable beforehand (i.e. self).
            expensiveObjectLazyAccess = [result]() { return result.get(); };

            // Initialization is done, call self again. I'm calling self->GetExpensiveObject() just to
            // illustrate that it's safe to call method on local copy of this. Using this->GetExpensiveObject()
            // would be undefined behavior since the reassignment above destroys the lambda captured 
            // variables. Alternatively I could just use:
            // return result.get();
            return self->GetExpensiveObject();
        };
    }

    ExpensiveType* GetExpensiveObject() const 
    {
        // Forward call to member function
        return expensiveObjectLazyAccess();
    }
private:
    // hold a function returning the value instead of the value itself
    std::function<ExpensiveType*()> expensiveObjectLazyAccess;
};

Main idea is to hold a function returning the expensive object as member instead of the object itself. In the constructor initialize with a function that does the following:

  • Initializes the expensive object
  • Replaces itself with a function which captures the already initialized object and just returns it.
  • Returns the object.

What I like about this is that the initialization code is still written in the constructor (where I'd naturally put it if lazyness was not needed) even though it will only get executed when the first query for the expensive object happens.

A downside of this approach is that std::function reassigns itself within it's execution. Accessing any non static members (captures in case of using lambda's) after the reassignment would result in undefined behavior so this requires extra attention. Also this is kind of a hack since GetExpensiveObject() is const but it still modifies a member attribute on first call.

In production code I'd probably prefer to just make the member mutable as James Curran described. This way the public API of your class clearly states that the member is not considered part of the objects state, therefore it doesn't affect constness.

After a bit more thinking I figured that std::async with std::launch::deferred could also be used in combination with a std::shared_future in order to be able to retrieve the result multiple times. Here is the code:

class MyClass
{
public:
    MyClass() :
        deferredObj()
    {
        deferredObj = std::async(std::launch::deferred, []()
        {
            return std::shared_ptr<ExpensiveType>(CreateExpensiveObject());
        });
    }

    const ExpensiveType* GetExpensiveObject() const
    {
        return deferredObj.get().get();
    }
private:
    std::shared_future<std::shared_ptr<ExpensiveType>> deferredObj;
};
凉风有信 2024-09-19 22:18:39

你的 getter 并不是真正的 const,因为它确实改变了对象的内容。我觉得你想多了。

Your getter isn't really const since it does change the content of the object. I think you're over thinking it.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文