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

C++ C++;:仅接受字符串文本的构造函数,c++,constructor,overloading,string-literals,C++,Constructor,Overloading,String Literals,是否可以创建一个只接受字符串文字而不接受字符常量*的构造函数(或函数签名) 是否可以有两个重载来区分字符串文本和char const* C++0x可能会允许使用自定义后缀,但我正在寻找一个“更早”的解决方案 基本原理:避免以字符串文本形式提供时不会修改的字符串的堆副本 这些字符串直接进入一个API,该API需要一个const char*,无需任何处理。大多数调用确实使用不需要额外处理的文本,只有在少数情况下它们是构造的。我正在寻找保留本机调用行为的可能性 注意:-因为它出现在答案中:所讨论的代码

是否可以创建一个只接受字符串文字而不接受字符常量*的构造函数(或函数签名)

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

C++0x可能会允许使用自定义后缀,但我正在寻找一个“更早”的解决方案

基本原理:避免以字符串文本形式提供时不会修改的字符串的堆副本

这些字符串直接进入一个API,该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; }
}
用一个简单的

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

这并不能阻止任何人滥用它(我应该找到一个更好的名称…),但它允许进行必要的优化,但在默认情况下仍然是安全的。

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

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

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

工作解决方案基于:

struct char\u包装器
{
字符包装器(constchar*val):val(val){};
常量字符*val;
};
类MyClass{
公众:
模板
显式MyClass(常量字符(&str)[N])
{

cout在某些平台上,为了让程序从只读内存访问文本,我不得不将字符串文本声明为
static const char*
。当声明为
const char*
时,程序集列表显示文本是从ROM复制到堆栈变量上的


与其担心接收者,不如尝试使用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);
}
void f1(Literal){}//只接受文本。
int main(){
auto str1=“我的天哪!我的天哪!”;

std::cout是的,它可以完成!我想出了一个解决方案,它可以与C++03一起工作,而不需要包装器类(它会破坏返回语句中的一些隐式转换)

首先,您需要为类型
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

我不这么认为,因为
string
有一个构造函数,它采用
char-const*
。你能详细说明一下原理吗?我不太明白为什么你希望复制字符串文本,而不复制“const-char*”(或者相反)。不允许您以完全相同的方式更改这两个对象。您还必须在析构函数中维护一个标志和条件代码。似乎您必须使用字符串文字创建比我通常使用的多得多的对象,才能使这项工作有价值。@Neil:我在不同的场景中反复遇到这种模式。我同意这一点在大多数情况下,副本并不重要——但在编写库时,您不知道自己是被调用了10次还是1000万次。@ypnpos:问题是:可以避免将初始副本复制到std::string中吗?可能是因为它不起作用——它首先会将字符数组识别为字符串文本。尽管如此,使用+1:无法区分“const char[]”和字符串文字,因此这是最佳解决方案。在C++03中,可以通过检查它们是否同时转换为
char*
const char[]来将它们分开
:如果他们这样做,它就是一个字符串文字,如果他们不这样做,它就是一个普通数组。但是在C++0x中,他们删除了从字符串文字到
char*
的不推荐的转换,因此这种破解不再有效:)@Johannes:我相信没有人想到过这种转换的用途!可能是唯一的“安全”转换这样做的方式,但我不想依赖于太多不可移植的诡计。无论如何,谢谢!这对C++14还是正确的吗?嗯,也许可以使用用户定义的文本?我认为这应该被接受为有效的答案。如果一个常量是一个字符缓冲区呢?那么我相信模板Foo(const char(&)[N]){BARK;}将被使用。一个常见的模式是将常量类ref传递给方法。如果该类具有非常量字符数组成员,它将从参数ref“继承”常量。如果将该成员传递给Foo::Foo(),它将使用Foo::Foo(const char(&)[]),它将认为它是文本,但实际上不是。
void f1(Literal) {}  // Accepts literals only.

int main() {
  auto str1 = "OMG! Teh Rey!"_L;
  std::cout << str1 << std::endl;
  f(str1);
}
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
#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;
}