C ++:仅接受字符串文字的构造函数

Posted

技术标签:

【中文标题】C ++:仅接受字符串文字的构造函数【英文标题】:C++: Constructor accepting only a string literal 【发布时间】:2011-01-03 17:21:58 【问题描述】:

是否可以创建一个构造函数(或函数签名,就此而言),接受字符串文字,但不接受例如char const *?

是否可以有两个重载可以区分字符串文字和char const *

C++ 0x 会允许使用自定义后缀 - 但我正在寻找“早期”解决方案。

基本原理:避免在以字符串文字形式给出时不会被修改的字符串堆副本。

这些字符串直接发送到 API,需要 const char *,无需任何处理。大多数调用确实使用不需要额外处理的文字,仅在少数情况下它们是构造的。我正在寻找保留本机调用行为的可能性。

注意: - 因为它出现在答案中:有问题的代码根本不使用std::string,但一个很好的例子是:

class foo

   std::string m_str;
   char const * m_cstr;      
 public:
   foo(<string literal> s) : m_cstr(p) 
   foo(char const * s) : m_str(s)  m_cstr = s.c_str(); 
   foo(std::string const & s) : m_str(s)  m_cstr = s.c_str(); 

   operator char const *() const  return m_cstr; 


结果:

(1) 做不到。 (2) 我意识到我什至不是在寻找文字,而是在寻找编译时间常数(即“任何不需要复制的东西”)。

我可能会改用以下模式:

const literal str_Ophelia = "Ophelia";

void Foo()

  Hamlet(str_Ophelia, ...);  // can receive literal or string or const char *

用一个简单的

struct literal  
 
   char const * data; 
   literal(char const * p) : data(p)  
   operator const char *() const  return data; 
;

这并不能阻止任何人滥用它(我应该找到一个更好的名字......),但它允许所需的优化,但默认情况下仍然是安全的。

【问题讨论】:

我不这么认为,因为string 有一个接受char const * 的构造函数。 您能详细说明原因吗?我不太明白为什么您希望复制字符串文字,而不是复制“const char *”(或相反)。您不能以完全相同的方式更改两者。 您还必须维护一个标志并在您的析构函数中包含条件代码。看起来你必须使用字符串字面量创建比我通常做的更多的对象才能做到这一点。 @Neil:我在不同的场景中反复绊倒这种模式。我同意在大多数地方,副本并不重要——但是在编写库时,你不知道你被调用了 10 次还是 1000 万次。 @ypnpos:问题是:可以避免初始复制到 std::string 吗? 【参考方案1】:

基于sbi idea的工作解决方案:

struct char_wrapper

    char_wrapper(const char* val) : val(val) ;
    const char* val;
;

class MyClass 
public:
  template< std::size_t N >
  explicit MyClass(const char (&str)[N])
  
      cout << "LITERAL" << endl;
  
  template< std::size_t N >
  explicit MyClass(char (&str)[N])
  
      cout << "pointer" << endl;
      
  MyClass(char_wrapper m)
  
     cout << "pointer" << endl;
  
;

int main()

    MyClass z("TEST1");     // LITERAL
    const char* b = "fff";
    MyClass a(b);           // pointer
    char tmp[256]; 
    strcpy(tmp, "hello"); 
    MyClass c(tmp);         // pointer

【讨论】:

可能是它不起作用的事实 - 它首先将字符数组识别为字符串文字。 不过,解决重载解决问题的好主意。 +1:无法区分“const char []”和字符串文字,因此这是最佳解决方案。 在 C++03 中,您可以通过检查它们是否同时转换为 char*const char[] 来将它们分开:如果它们转换为,则为字符串文字,如果不转换,则为普通数组。但是在 C++0x 中,他们删除了从字符串文字到char* 的弃用转换,因此这个黑客将不再起作用:) @Johannes:这是对特定转换的使用,我敢肯定没有人设想过!【参考方案2】:

是的,它可以做到!我想出了一个适用于 C++03 且没有包装类的解决方案(这会破坏 return 语句中的一些隐式转换)。

首先,您需要const char (&amp;)[N] 类型的构造函数模板,因为这是字符串文字的原始类型。然后你还需要另一个类型 char (&amp;)[N] - 例如 char 缓冲区 - 这样它们就不会出现在文字的构造函数中。并且您可能还想要一个用于 const char* 类型的普通构造函数。

template<int N> Foo(const char (&)[N]); // for string literals
template<int N> Foo(char (&)[N]);       // for non-const char arrays like buffers
Foo(const char*);                       // normal c strings

现在的问题是,对于字符串文字,编译器仍然认为,const char* 构造函数是比模板实例更好的选择,因为 数组到指针的转换 具有 完全匹配排名。 (13.3.3.1.1)

所以,诀窍是降低const char* 构造函数的优先级。这也可以通过将其更改为模板并使用 SFINAE 将其仅与 const char* 类型匹配来完成。构造函数没有返回值,只有一个参数,这是类型推导所必需的。因此需要另一个具有默认值的“虚拟参数”,它使用条件类型特征:template&lt;typename T&gt; Foo(T, typename IsCharPtr&lt;T&gt;::Type=0)

解决方案:

#include <iostream>

#define BARK std::cout << __PRETTY_FUNCTION__ << std::endl

struct Dummy ;
template<typename T> struct IsCharPtr ;
template<> struct IsCharPtr<const char *>  typedef Dummy* Type; ;
template<> struct IsCharPtr<char *>  typedef Dummy* Type; ;

struct Foo 
  template<int N> Foo(const char (&)[N])  BARK; 
  template<int N> Foo(char (&)[N])  BARK; 
  template<typename T> Foo(T, typename IsCharPtr<T>::Type=0)  BARK; 
;

const char a[] = "x";
const char* b = "x";
const char* f()  return b; 

int main() 
  char buffer[10] = "lkj";
  char* c = buffer;
  Foo l("x");     // Foo::Foo(const char (&)[N]) [N = 2]
  Foo aa(a);      // Foo::Foo(const char (&)[N]) [N = 2]
  Foo bb(b);      // Foo::Foo(T, typename IsCharPtr<T>::Type) [T = const char *]
  Foo cc(c);      // Foo::Foo(T, typename IsCharPtr<T>::Type) [T = char *]
  Foo ee(buffer); // Foo::Foo(char (&)[N]) [N = 10]
  Foo ff(f());    // Foo::Foo(T, typename IsCharPtr<T>::Type) [T = const char *]
  return 0;

【讨论】:

我认为这应该被接受为有效答案。 如果一个 const_cast 是一个字符缓冲区怎么办?然后我相信 template Foo(const char (&)[N]) BARK; 将被使用。 一种常见的模式是将 const 类引用传递给方法。如果该类有一个非 const char 数组成员,它将“继承”参数 ref 的 const。如果成员被传递给 Foo::Foo() 它将使用 Foo::Foo( const char (&)[] ),它会认为它是一个文字,但实际上不是。【参考方案3】:

不,你不能这样做 - 字符串文字和 const char* 是可以互换的。一种解决方法可能是引入一个特殊的类来保存指向字符串文字的指针,并使构造函数只接受它。这样,每当您需要传递文字时,您都会调用该类的构造函数并传递临时对象。这并不能完全防止滥用,但会使代码更易于维护。

【讨论】:

C++14 仍然如此吗?嗯,也许可以使用用户定义的文字? en.cppreference.com/w/cpp/language/user_literal【参考方案4】:

如果你确切地知道你的编译器和平台是如何处理字符串文字的,那么就有可能编写一个可以做到这一点的解决方案。如果您知道您的编译器总是将字符串文字放入特定的内存区域,您可以根据该内存的边界检查指针。如果它属于该块,则您有一个字符串文字;否则你有一个字符串存储在堆或堆栈上。

但是,此解决方案将是特定于平台/编译器的。它不会是便携式的。

【讨论】:

可能是唯一的“安全”方式来做到这一点,但我不想依赖那么多不可移植的诡计。无论如何,谢谢!【参考方案5】:

在某些平台上,我必须将字符串文字声明为static const char *,以便程序能够访问只读内存中的文本。当声明为const char * 时,程序集列表显示文本已从 ROM 复制到堆栈变量中。

与其担心接收者,不如尝试使用static const char * 声明字符串文字。

【讨论】:

【参考方案6】:

使用 C++14 中的新用户定义文字(至于 Clang 3.5 - 它也适用于 C++11),有一个优雅的解决方案:

class Literal 
 public:
  explicit Literal(const char* literal) : literal_(literal) 
  // The constructor is public to allow explicit conversion of external string
  // literals to `_L` literals. If there is no such need, then move constructor
  // to private section.

  operator const char* ()  return literal_; 

 private:
  friend Literal operator"" _L (const char*, unsigned long);
  // Helps, when constructor is moved to private section.

  const char* literal_;
;

Literal operator"" _L (const char* str, unsigned long) 
  return Literal(str);

可以这样使用:

void f1(Literal)   // Accepts literals only.

int main() 
  auto str1 = "OMG! Teh Rey!"_L;
  std::cout << str1 << std::endl;
  f(str1);

有一个缺点:您必须将 _L 附加到每个字面值 - 但这没什么大不了的,真的。

【讨论】:

以上是关于C ++:仅接受字符串文字的构造函数的主要内容,如果未能解决你的问题,请参考以下文章

为什么std :: pair类标准被改为禁止在C ++ 11中只有非常量复制构造函数的类型?

通过字符串创建代表c ++中类名的新对象[重复]

缺少 QLayout 构造函数来实现卡片布局

每周小贴士#142:多参数构造函数和explicit

每周小贴士#142:多参数构造函数和explicit

C 中的递归练习