Is there a way to prevent passing “new T” to my observer_ptr constructor?

huangapple go评论57阅读模式
英文:

Is there a way to prevent passing "new T" to my observer_ptr constructor?

问题

我想实现自己的observer_ptr类,我希望用它来指示不拥有指针。我想知道是否有可能阻止某人使用以下语法:

observer_ptr<T> var_name(new T);

因为这会完全使这个类的目的失效,并导致内存泄漏。我猜想我需要删除/私有化某种类型的构造函数,但我不知道应该选择什么参数类型来实现这一点。

我不是在编写任何可共享的库,我更多地是在为自己提问,以便学习。

英文:

I want to implement my own observer_ptr class which I want to use to indicate no ownership of a pointer. I'm wondering if it'd be possible to somehow prevent someone from using the following syntax:

observer_ptr<T> var_name(new T);

since it would completely invalidate the whole purpose of this class and lead to memory leaks. My guess would be that I would have to delete/make private a certain type of constructor, but I don't know what argument type I should choose to achieve that.

I'm not writing any sharable library, I'm asking more for myself so I can learn.

答案1

得分: 3

If you accept the input T* pointer by a non-const reference, users can't call new directly in the constructor call, as the reference won't be able to bind to the temporary that new returns, eg:

如果你通过非常量引用接受输入的 `T*` 指针,用户将无法直接在构造函数调用中调用 `new`,因为该引用无法绑定到 `new` 返回的临时对象,例如:

```c++

They would be required to use a local T* variable instead, making them more aware of their responsibility for the lifetime of the object they create, eg:

他们将需要使用本地的 `T*` 变量,以代替,从而更加意识到他们对所创建对象的生命周期负有责任,例如:

```c++

Note, however, that this approach does prevent users from being able to directly observe non-dynamic objects whose addresses are taken by operator& or std::addressof(), as well as unique_ptr and shared_ptr objects, eg:

但是请注意,这种方法阻止用户能够*直接*观察由 `operator&`  `std::addressof()` 获取地址的非动态对象,以及 `unique_ptr`  `shared_ptr` 对象,例如:

```c++

This would also require the use of a local T* pointer, eg:

这还需要使用本地的 `T*` 指针,例如:

```c++

You can avoid that if you add some additional constructors to observer_ptr, eg:

如果你为 `observer_ptr` 添加一些额外的构造函数,你可以避免这一点,例如:

```c++

I hope this helps! Is there anything else you'd like to know?

英文:

If you accept the input T* pointer by a non-const reference, users can't call new directly in the constructor call, as the reference won't be able to bind to the temporary that new returns, eg:

template <typename T>
class observer_ptr
{
public:
    observer_ptr(T*& ptr);
    ...
};
observer_ptr<T> var_name(new T); // error!

They would be required to use a local T* variable instead, making them more aware of their responsibility for the lifetime of the object they create, eg:

T *ptr = new T;
observer_ptr<T> var_name(ptr); // OK!

Note, however, that this approach does prevent users from being able to directly observe non-dynamic objects whose addresses are taken by operator& or std::addressof(), as well as unique_ptr and shared_ptr objects, eg:

T obj;
observer_ptr<T> var_name(&obj); // error!
auto ptr = make_unique<T>(); // or make_shared()
observer_ptr<T> var_name(ptr.get()); // error!

This would also require the use of a local T* pointer, eg:

T obj;
T* ptr = &obj;
observer_ptr<T> var_name(ptr); // OK!
auto obj = make_unique<T>(); // or make_shared()
T* ptr = obj.get();
observer_ptr<T> var_name(ptr); // OK!

You can avoid that if you add some additional constructors to observer_ptr, eg:

template <typename T>
class observer_ptr
{
private:
    T* m_ptr;
public:
    observer_ptr(T*& ptr) : m_ptr(ptr) {}
    observer_ptr(T& obj) : m_ptr(addressof(obj)) {}
    observer_ptr(const unique_ptr<T>& ptr) : m_ptr(ptr.get()) {}
    observer_ptr(const shared_ptr<T>& ptr) : m_ptr(ptr.get()) {}
    observer_ptr(unique_ptr<T>&&) = delete;
    observer_ptr(shared_ptr<T>&&) = delete;
    ...
};
T obj;
observer_ptr<T> var_name(obj); // OK!
auto ptr = make_unique<T>(); // or make_shared()
observer_ptr<T> var_name(ptr); // OK!
observer_ptr<T> var_name(make_unique<T>()); // error!
observer_ptr<T> var_name(make_shared<T>()); // error!

答案2

得分: 1

以下是翻译好的部分:

"不可能对构造函数参数设置这样的限制。如果您使用指针,它可以是任何兼容类型的指针。

但是在我看来,这不是您应该通过语言来强制执行的事情,而应该通过契约和类的意图来实现,因为如果您查看智能指针类,它们也接受任何指针类型,并且在涉及它们接受什么类型的指针时完全依赖于文档和常识:

int stackValue;
std::unique_ptr myPtr{ &stackValue }; // 在myPtr销毁时它将是未定义行为,但仍然可以编译

此外,原始指针 T* 是表达对象不拥有某个指针的传统方式。因此,您可能正在重新发明轮子,可能需要重新考虑这个意图。"

英文:

It's impossible to put such a restriction on the constructor parameter. If you take a pointer, it can be any pointer (of compatible type).

However in my opinion it's not something you should enforce with the language, but rather with a contract and the class intention, because if you look at smart pointer classes they also accept any pointer type and completely rely on documentation and common sense when it comes to what kind of pointer they take:

int stackValue;
std::unique_ptr myPtr{ &stackValue }; // it will be UB upon myPtr destruction but compiles anyway

Also, a raw pointer T* is conventional way of expressing some pointer an object doesn't own. So you might be re-inventing the wheel here and may want to rethink this intention.

答案3

得分: 0

你需要能够传递智能指针或引用来创建observer_ptr,用户始终有可能搞砸这个操作。不过,你可以通过更明显的方式让头文件的用户表明他们的意图,希望他们能够认真思考自己的代码以自行捕获错误。

你可以通过添加一个用于创建observer_ptr的函数,并通过可见性限制要求其使用来实现这一点。以下示例要求用户只能使用 make_unowned_ptr(...) 来创建除了空指针之外的任何 observer_ptr

template<class T>
class observer_ptr;

template<class T>
constexpr observer_ptr<T> make_unowned_ptr(T* ptr)
{
    return { ptr };
}

template<class T>
class observer_ptr
{
    T* m_ptr;

    constexpr observer_ptr(T* ptr)
        : m_ptr(ptr)
    {
    }

    friend constexpr observer_ptr<T> make_unowned_ptr<T>(T*);
public:
    constexpr observer_ptr(std::nullptr_t = nullptr)
        : m_ptr(nullptr)
    {
    }
};

int main()
{
    int value = 1;
    observer_ptr<int> ptr1(&value); // 编译错误: 构造函数不可见
    auto ptr2 = make_unowned_ptr(&value); // 可行
}

希望这有助于你的理解。

英文:

One way or another you'll need to be able to pass a (smart) pointer or a reference to create an observer_ptr and the user will always be able to screw this up. You could make the user of your header state in a more obvious way though what they intend to do and hopefully have them think enough about the code they're writing to catch the error themselves.

You could accomplish this by adding a function for creating the observer_ptr and requiring its use via visibility restrictions. The following example requires the user to use make_unowned_ptr(...) for creating an observer_ptr for anything other than a null pointer:

template&lt;class T&gt;
class observer_ptr;

template&lt;class T&gt;
constexpr observer_ptr&lt;T&gt; make_unowned_ptr(T* ptr)
{
    return { ptr };
}

template&lt;class T&gt;
class observer_ptr
{
    T* m_ptr;

    constexpr observer_ptr(T* ptr)
        : m_ptr(ptr)
    {
    }

    friend constexpr observer_ptr&lt;T&gt; make_unowned_ptr&lt;T&gt;(T*);
public:
    constexpr observer_ptr(std::nullptr_t = nullptr)
        : m_ptr(nullptr)
    {
    }
};

int main()
{
    int value = 1;
    observer_ptr&lt;int&gt; ptr1(&amp;value); // compiler error: constructor not visible
    auto ptr2 = make_unowned_ptr(&amp;value); // ok
}

huangapple
  • 本文由 发表于 2023年5月18日 00:23:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/76274245.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定