当类对象超出范围之前可以删除数据时,如果类允许访问其数据(通过 ptr/it),这是否是糟糕的设计?

发布于 2024-11-06 04:14:22 字数 496 浏览 0 评论 0原文

经典的例子是迭代器失效:

std::string test("A");
auto it = test.insert(test.begin()+1,'B');
test.erase();
...
std::cout << *it;

您是否认为这种 API 是糟糕的设计,并且对于初学者来说很难学习/使用?

在这种情况下,一个成本高昂、性能/内存方面的解决方案是,当使用clear方法时,将指针/迭代器分配给空字符串(或nullptr,但这不是很有帮助)。

一些精度

我正在考虑这种设计,用于返回可以在内部修改的 const chars* (也许它们存储在可以清除的 std::vector 中)。我不想返回 std::string (二进制兼容性),也不想使用 get(char*,std::size_t) 方法,因为需要获取大小参数(太慢)。另外,我不想围绕 std::string 或我自己的字符串类创建包装器。

Classic example is iterator invalidation :

std::string test("A");
auto it = test.insert(test.begin()+1,'B');
test.erase();
...
std::cout << *it;

Do you think having this kind of API is bad design, and will be difficult to learn/use for beginners ?

A costly, performance/memory wise, solution would be, in that type of case, to assign the pointer/iterator to an empty string (or a nullptr, but that's not very helpful) when a clear method is used.

Some precisions

I'm thinking of this design for returning const chars* that can be modified internally (maybe they're stored in a std::vector that can be cleared). I don't want to return a std::string (binary compatibility) and I don't want a get(char*,std::size_t) method because of the size argument that needs to be fetched (too slow). Also I don't want to create a wrapper around std::string or my own string class.

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

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

发布评论

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

评论(3

恏ㄋ傷疤忘ㄋ疼 2024-11-13 04:14:22

我建议阅读Stepanov的设计理念(第9-11页):

[这个例子]是以清晰的面向对象风格编写的,带有 getter 和 setter。这种风格的支持者表示,拥有此类函数的优点是它允许程序员稍后更改实现。他们忘记提到的是,有时公开实现是非常好的。让我们看看我的意思。我很难想象一个系统的演变可以让您保留 get 和 set 的接口,但能够更改实现。我可以想象,该实现超出了 int 的范围,您需要切换到 long。但那是一个不同的界面。我可以想象您决定从数组切换到列表,但这也会迫使您更改界面,因为索引到链接列表确实不是一个好主意。

现在让我们看看为什么公开实现真的很好。让我们假设明天您决定对整数进行排序。你怎么能做到呢?您可以使用Cqsort吗?不,因为它对你的 getter 和 setter 一无所知。你能使用 STL sort 吗?答案是一样的。虽然您设计的类能够承受实现中的一些假设变化,但您并没有将其设计用于非常常见的排序任务。当然,getter 和 setter 的支持者会建议您使用成员函数 sort 来扩展您的接口。这样做之后,您会发现您需要二分搜索和中位数等。很快您的类将拥有 30 个成员函数,但当然,它将隐藏实现。只有当您是班级的所有者时才能做到这一点。否则,您需要从头开始在 setter-getter 接口之上实现一个像样的排序算法,这是一项比人们想象的更加困难和危险的活动。 ...

Setter 和 getter 使我们的日常编程变得困难,但当我们发现在内存中存储整数数组的更好方法时,它会在未来带来巨大的回报。但我不知道在我们的数据结构中隐藏内存位置有什么帮助,而暴露出来会造成伤害。因此,我有义务公开一个更方便的接口,该接口也恰好与熟悉的 C 数组接口一致。当我们使用 C++ 编程时,我们不应该为它的 C 传统感到羞耻,而应该充分利用它。 C++ 的唯一问题,甚至是 C 的唯一问题,都是在它们本身与自己的逻辑不一致时出现的。 ...

我关于公开连续整数的地址位置的言论并不是开玩笑。
我们花了很大的努力才让标准委员会相信这样的要求是
向量的基本属性;然而,他们不同意向量迭代器应该
是指针,因此,在几个主要平台上 - 包括 Microsoft 平台 - 它
通过说令人难以置信的丑陋来对矢量进行排序更快

if (!v.empty()) {
    排序(&*v.begin(), &*v.begin() + v.size());
}

比预期的要多

sort(v.begin(), v.end());

以牺牲效率为代价强加伪抽象的尝试可能会失败,但代价却是可怕的。

Stepanov 还有很多其他有趣的文档,特别是在“课堂笔记”部分。

是的,有一些关于 OOP 的经验法则。不,我不相信它们真的是最好的做事方式。当您使用 STL 时,以 STL 兼容的方式做事很有意义。当您的抽象级别较低时(例如 std::vector ,专门用于使动态分配的数组更容易使用;即,它应该几乎像具有一些附加功能的数组一样可用),那么一些 OOP 经验法则就完全没有意义了。

回答最初的问题:即使是初学者最终也需要了解迭代器、对象生命周期以及我所说的对象的使用寿命(即“对象尚未超出范围,但不再有效使用” ,就像一个无效的迭代器”)。我认为没有任何理由试图向用户隐藏这些生活事实,因此我个人不会基于这些理由排除基于迭代器的 API。真正的问题是您的 API 旨在抽象什么以及它旨在公开什么(类似于矢量是一个更好的数组并且旨在公开其数组性质这一事实)。如果您回答这个问题,您应该更好地了解基于迭代器的 API 是否有意义。

I would recommend reading up on Stepanov's design philosophy (pages 9-11):

[This example] is written in a clear object-oriented style with getters and setters. The proponents of this style say that the advantage of having such functions is that it allows programmers later on to change the implementation. What they forget to mention is that sometimes it is awfully good to expose the implementation. Let us see what I mean. It is hard for me to imagine an evolution of a system that would let you keep the interface of get and set, but be able to change the implementation. I could imagine that the implementation outgrows int and you need to switch to long. But that is a different interface. I can imagine that you decide to switch from an array to a list but that also will force you to change the interface, since it is really not a very good idea to index into a linked list.

Now let us see why it is really good to expose the implementation. Let us assume that tomorrow you decide to sort your integers. How can you do it? Could you use the C library qsort? No, since it knows nothing about your getters and setters. Could you use the STL sort? The answer is the same. While you design your class to survive some hypothetical change in the implementation, you did not design it for the very common task of sorting. Of course, the proponents of getters and setters will suggest that you extend your interface with a member function sort. After you do that, you will discover that you need binary search and median, etc. Very soon your class will have 30 member functions but, of course, it will be hiding the implementation. And that could be done only if you are the owner of the class. Otherwise, you need to implement a decent sorting algorithm on top of the setter-getter interface from scratch and that is a far more difficult and dangerous activity than one can imagine. ...

Setters and getters make our daily programming hard but promise huge rewards in the future when we discover better ways to store arrays of integers in memory. But I do not know a single realistic scenario when hiding memory locations inside our data structure helps and exposure hurts; it is, therefore, my obligation to expose a much more convenient interface that also happens to be consistent with the familiar interface to the C arrays. When we program in C++ we should not be ashamed of its C heritage, but make full use of it. The only problems with C++, and even the only problems with C, arise when they themselves are not consistent with their own logic. ...

My remark about exposing the address locations of consecutive integers is not facetious.
It took a major effort to convince the standard committee that such a requirement is an
essential property of vectors; they would not, however, agree that vector iterators should
be pointers and, therefore, on several major platforms – including the Microsoft one – it
is faster to sort your vector by saying the unbelievably ugly

if (!v.empty()) {
    sort(&*v.begin(), &*v.begin() + v.size());
}

than the intended

sort(v.begin(), v.end());

Attempts to impose pseudo-abstractness at the cost of efficiency can be defeated, but at a terrible cost.

Stepanov has a lot of other interesting documents available, especially in the "Class Notes" section.

Yes, there are several rules of thumb regarding OOP. No, I'm not convinced that they are really the best way to do things. When you're working with the STL it makes a lot of sense to do things the STL compatible way. And when your abstraction is low level (like std::vector, which is meant specifically to make working with dynamically allocated arrays easier; i.e., it should be usable almost like an array with some added features), then some of those OOP rules of thumb make no sense at all.

To answer the original question: even beginners will eventually need to learn about iterators, object lifetimes, and what I'll call an object's useful life (i.e., "the object hasn't fallen out of scope, but is no longer valid to use, like an invalidated iterator"). I don't see any reason to try to hide those facts of life from the user, so I personally wouldn't rule out an iterator-based API on those grounds. The real question is what your API is meant to abstract and what's it's meant to expose (similar to the fact that a vector is a nicer array and is meant to expose its array nature). If you answer that, you should have a better idea about whether an iterator-based API makes sense.

笑着哭最痛 2024-11-13 04:14:22

正如 Scott Meyers 在《Effective C++》中所说:是的,通过指针、迭代器或引用授予私有/受保护成员的访问权限确实不是一个好的设计,因为你永远不知道客户端代码会用它做什么。

据我所知,应该避免这种情况,有时最好创建数据成员的副本,然后将其返回给调用者。

As Scott Meyers states in Effective C++: yes it is indeed not a good design to grant access to private/protected members via pointers, iterators or references because you never know what the client code will do with it.

As far as I can remember this should be avoided, and it is sometimes better to create a copy of data members which are then returned to the caller.

◇流星雨 2024-11-13 04:14:22

这是一个糟糕或错误的实现,而不是设计。
至于通过指针提供对私有或受保护成员的访问,基本上它破坏了抽象的基本OOP原则之一。

我不确定问题是什么,是的,当然,实现使迭代器无效的实现是不好的。这里真正的Q是什么?

It is a bad or faulty implementation rather than design.
As for providing access to private or protected members through pointers, basically it destroys one of the basic OOP principle of Abstraction.

I am unsure though as to what the question is, Yes ofcourse it is bad to have implementation which invalidates iterator. What is the real Q here?

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