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

时间:2010-01-11 11:03:06

标签: c++ constructor overloading string-literals

是否可以创建接受字符串文字的构造函数(或函数签名),而不是例如 char const *

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

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

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

这些字符串直接转到期望const char *的API而不进行任何处理。大多数调用都使用不需要额外处理的文字,只在少数情况下构建它们。我正在寻找保留原生呼叫行为的可能性。

注意: - 因为它出现在答案中:相关代码根本不使用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; }
};

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

6 个答案:

答案 0 :(得分:19)

基于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 :(得分:18)

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

首先,您需要类型const char (&)[N]的构造函数模板,因为这是字符串文字的原始类型。然后你还需要另一个类型char (&)[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*构造函数是比模板实例更好的选择,因为数组到指针的转换具有< em>完全匹配等级。 (13.3.3.1.1)

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

答案 2 :(得分:9)

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

答案 3 :(得分:3)

如果您确切知道编译器和平台如何处理字符串文字,则可以编写可以执行此操作的解决方案。如果您知道编译器始终将字符串文字放入特定的内存区域,则可以根据该内存的边界检查指针。如果它在那个区块内,你就有一个字符串文字;否则你有一个存储在堆或堆栈上的字符串。

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

答案 4 :(得分:0)

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

不要担心接收者,也许尝试使用static const char *声明字符串文字。

答案 5 :(得分:0)

在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附加到每个字面上 - 但这确实不是什么大问题。