一个循环的const错误 [英] A recurring const-connundrum

查看:185
本文介绍了一个循环的const错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我经常发现自己必须定义一个函数的两个版本,以便有一个是const和一个非const(通常是一个getter,但不总是)。两者的不同之处仅在于一个的输入和输出是const,而另一个的输入和输出是非const。函数的胆量 - 真正的工作,是IDENTICAL。



然而,对于const正确性,我需要他们。作为一个简单的实际示例,请执行以下操作:

  inline const ITEMIDLIST * GetNextItem(const ITEMIDLIST * pidl)
{
return pidl? reinterpret_cast< const ITEMIDLIST *>(reinterpret_cast< const BYTE *>(pidl)+ pidl-> mkid.cb):NULL;
}

inline ITEMIDLIST * GetNextItem(ITEMIDLIST * pidl)
{
return pidl? reinterpret_cast< ITEMIDLIST *>(reinterpret_cast< BYTE *>(pidl)+ pidl-> mkid.cb):NULL;
}

正如你所看到的,他们做同样的事情。我可以选择使用更多的强制转换来定义其中一个,这是更适当的,如果勇气 - 实际工作,不太平凡:

  inline const ITEMIDLIST * GetNextItem(const ITEMIDLIST * pidl)
{
return pidl? reinterpret_cast< const ITEMIDLIST *>(reinterpret_cast< const BYTE *>(pidl)+ pidl-> mkid.cb):NULL;
}

inline ITEMIDLIST * GetNextItem(ITEMIDLIST * pidl)
{
return const_cast< ITEMIDLIST *>(GetNextItem(const_cast< const ITEMIDLIST *>(pidl ));
}

所以,我觉得这很可怕和冗长。希望编写const正确的代码,然后我要么提供以上两个,或者我不得不放弃我的消费者代码使用const方法来解决只有定义一个或另一个的问题。 p>

这是否有更好的模式?您认为此问题的最佳方法是什么:




  • 提供给定函数的两个副本 - 常量和非常量版本

  • 或只有一个版本,然后要求该代码的消费者执行他们的转换他们会吗?



或者有更好的方法来解决这个问题吗?
正在做语言本身的工作




  • 你发现这是C ++ const系统的一个不幸的副产品

  • 或者你觉得这等于碰到奥林匹斯山的高度吗?



编辑:



如果我只提供第一个 - 需要修改返回的项目,或者将返回的项目移交给修改它的另一个函数,必须抛弃constness。



同样,如果我只提供第二个定义 - 使用非常量并返回非常量,那么具有常量pidl的消费者必须抛弃常量以便使用上述函数,这诚实地不会修改项本身的常量。



也许更抽象是可取的:

  THING& Foo(THING& it); 
const THING& Foo(const THING& it);

我喜欢有一个结构:

  const_neutral THING& Foo(const_neutral THING& it); 

我当然可以这样做:

  THING& Foo(const THING& it); 

但这总是让我错误的方式。我在说我不修改你的THING的内容,但我要摆脱你托付给我在你的代码中为你的常理。



现在,客户端包含:

  const THING& it = GetAConstThing(); 
...
ModifyAThing(Foo(it));

这是错误的。 GetAConstThing与调用者的合同是给它一个const引用。调用者期望不要修改的东西 - 只使用const操作就可以了。是的,调用者可以是邪恶的,抛弃它的常态,但只是邪恶(tm)。



问题的症结, Foo是const中性。它不会实际修改它的给定的事情,但它的输出需要传播其参数的常数。



注意:编辑第二次格式化。

解决方案

我不相信这是const正确性的缺陷本身,而是缺乏方便的方法通过cv -qualifiers(同样的方式,我们可以通过模板泛化类型)。假设你可以写如下:

 模板< cvqual CV> 
inline CV ITEMIDLIST * GetNextItem(CV ITEMIDLIST * pidl)
{
return pidl? reinterpret_cast< CV ITEMIDLIST *>(reinterpret_cast< CV BYTE *>(pidl)+ pidl-> mkid.cb):NULL;
}

ITEMIDLIST o;
const ITEMIDLIST co;


ITEMIDLIST * po = GetNextItem(& o); // CV被推导为无关
ITEMIDLIST * pco = GetNextItem(& co); // CV被推导为const

现在你可以用模板做这种事metaprogramming,但是这得到
凌乱真实的:

  template< class T,class TProto> 
struct make_same_cv_as {
typedef T result;
};

template< class T,class TProto>
struct make_same_cv_as< T,const TProto> {
typedef const T result;
};

template< class T,class TProto>
struct make_same_cv_as< T,volatile TProto> {
typedefvolatile T result;
};

template< class T,class TProto>
struct make_same_cv_as< T,const volatile TProto> {
typedef const volatile T result;
};

模板< class CV_ITEMIDLIST>
inline CV_ITEMIDLIST * GetNextItem(CV_ITEMIDLIST * pidl)
{
return pidl? reinterpret_cast< CV_ITEMIDLIST *>(reinterpret_cast< typename make_same_cv_as< BYTE,CV_ITEMIDLIST> :: result *>(pidl)+ pidl-> mkid.cb):NULL;
}

上述问题是所有模板的常见问题 - 让你传递任何随机类型的对象,只要它有成员具有正确的名称,而不只是 ITEMIDLIST 。当然,你可以使用各种静态断言实现,但这也是一个黑客本身。



或者,你可以使用模板版本重用代码在你的.cpp文件中,然后将其包装到一个const /非const对,并在标题中公开。这样,你几乎只有重复的函数签名。


I often find myself having to define two versions of a function in order to have one that is const and one which is non-const (often a getter, but not always). The two vary only by the fact that the input and output of one is const, while the input and output of the other is non-const. The guts of the function - the real work, is IDENTICAL.

Yet, for const-correctness, I need them both. As a simple practical example, take the following:

inline const ITEMIDLIST * GetNextItem(const ITEMIDLIST * pidl)
{
    return pidl ? reinterpret_cast<const ITEMIDLIST *>(reinterpret_cast<const BYTE *>(pidl) + pidl->mkid.cb) : NULL;
}

inline ITEMIDLIST * GetNextItem(ITEMIDLIST * pidl)
{
    return pidl ? reinterpret_cast<ITEMIDLIST *>(reinterpret_cast<BYTE *>(pidl) + pidl->mkid.cb) : NULL;
}

As you can see, they do the same thing. I can choose to define one in terms of the other using yet more casts, which is more appropriate if the guts - the actual work, is less trivial:

inline const ITEMIDLIST * GetNextItem(const ITEMIDLIST * pidl)
{
    return pidl ? reinterpret_cast<const ITEMIDLIST *>(reinterpret_cast<const BYTE *>(pidl) + pidl->mkid.cb) : NULL;
}

inline ITEMIDLIST * GetNextItem(ITEMIDLIST * pidl)
{
    return const_cast<ITEMIDLIST *>(GetNextItem(const_cast<const ITEMIDLIST *>(pidl));
}

So, I find this terribly tedious and redundant. But if I wish to write const-correct code, then I either have to supply both of the above, or I have to litter my "consumer-code" with const-casts to get around the problems of having only defined one or the other.

Is there a better pattern for this? What is the "best" approach to this issue in your opinion:

  • providing two copies of a given function - the const and non-const versions
  • or just one version, and then requiring consumers of that code to do their casts as they will?

Or is there a better approach to the issue entirely? Is there work being done on the language itself to mitigate or obviate this issue entirely?

And for bonus points:

  • do you find this to be an unfortunate by-product of the C++ const-system
  • or do you find this to be tantamount to touching the very heights of mount Olympus?

EDIT:

If I supply only the first - takes const returns const, then any consumer that needs to modify the returned item, or hand the returned item to another function that will modify it, must cast off the constness.

Similarly, if I supply only the second definition - takes non-const and returns non-const, then a consumer that has a const pidl must cast off the constness in order to use the above function, which honestly, doesn't modify the constness of the item itself.

Maybe more abstraction is desirable:

THING & Foo(THING & it);
const THING & Foo(const THING & it);

I would love to have a construct:

const_neutral THING & Foo(const_neutral THING & it);

I certainly could do something like:

THING & Foo(const THING & it);

But that's always rubbed me the wrong way. I am saying "I don't modify the contents of your THING, but I'm going to get rid of the constness that you entrusted me with silently for you in your code."

Now, a client, which has:

const THING & it = GetAConstThing();
...
ModifyAThing(Foo(it));

That's just wrong. GetAConstThing's contract with the caller is to give it a const reference. The caller is expected NOT TO MODIFY the thing - only use const-operations on it. Yes, the caller can be evil and wrong and cast away that constness of it, but that's just Evil(tm).

The crux of the matter, to me, is that Foo is const-neutral. It doesn't actually modify the thing its given, but its output needs to propagate the constness of its argument.

NOTE: edited a 2nd time for formatting.

解决方案

I don't believe it's the deficiency of const-correctness per se, but rather the lack of convenient ability to generalize a method over cv-qualifiers (in the same way we can generalize over types via templates). Hypothetically, imagine if you could write something like:

template<cvqual CV>
inline CV ITEMIDLIST* GetNextItem(CV ITEMIDLIST * pidl)
{
    return pidl ? reinterpret_cast<CV ITEMIDLIST *>(reinterpret_cast<CV BYTE *>(pidl) + pidl->mkid.cb) : NULL;
}

ITEMIDLIST o;
const ITEMIDLIST co;


ITEMIDLIST* po = GetNextItem(&o); // CV is deduced to be nothing
ITEMIDLIST* pco = GetNextItem(&co); // CV is deduced to be "const"

Now you can actually do this kind of thing with template metaprogramming, but this gets messy real quick:

template<class T, class TProto>
struct make_same_cv_as {
    typedef T result;
};

template<class T, class TProto>
struct make_same_cv_as<T, const TProto> {
    typedef const T result;
};

template<class T, class TProto>
struct make_same_cv_as<T, volatile TProto> {
    typedef volatile T result;
};

template<class T, class TProto>
struct make_same_cv_as<T, const volatile TProto> {
    typedef const volatile T result;
};

template<class CV_ITEMIDLIST>
inline CV_ITEMIDLIST* GetNextItem(CV_ITEMIDLIST* pidl)
{
    return pidl ? reinterpret_cast<CV_ITEMIDLIST*>(reinterpret_cast<typename make_same_cv_as<BYTE, CV_ITEMIDLIST>::result*>(pidl) + pidl->mkid.cb) : NULL;
}

The problem with the above is the usual problem with all templates - it'll let you pass object of any random type so long as it has the members with proper names, not just ITEMIDLIST. You can use various "static assert" implementations, of course, but that's also a hack in and of itself.

Alternatively, you can use the templated version to reuse the code inside your .cpp file, and then wrap it into a const/non-const pair and expose that in the header. That way, you pretty much only duplicate function signature.

这篇关于一个循环的const错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆