【问题标题】:How to assign a string literal如何分配字符串文字
【发布时间】:2017-08-13 08:15:10
【问题描述】:

我是一名 Java 程序员;我上一次写 C 或 C++ 是在 20 年前。现在我回来了,我正在尝试使用更现代的 C++,例如 C++11/C++14,并避免使用旧的 c 风格编程。

我如何分配一个字符串,就像在 Java 中一样:

private static final String MY_STRING = "Hello World"; // could also be public

现在我有 MY_STRING 到文本 "Hello World" 的引用,我可以在我的程序中使用任意多次,如果我想将其更改为 "Hello Dolly",我会准确地进行更改在一个地方。

我想在 C++ 中实现相同的目标。 示例:

JavaVMOption* options = new JavaVMOption[1];

options[0].optionString = (char *)"-Djava.class.path=.";

这行得通,没有更多问题,但我不喜欢它,因为要求只有在需要时才能在一个地方更改它。 所以,我声明(甚至在头文件中):

std::string classpath = "-Djava.class.path=.";
// or
const std::string classpath = "-Djava.class.path=.";

我把它用作:

options[0].optionString = (char *)classpath.c_str();

现在,使用 c_str() 函数,这也可以,但我又回到了普通的旧 C 语言!

我真的很想知道是否有办法保持在更现代的 C++ 水平?

是的,我知道,

JavaVMOption* options = new JavaVMOption[1]; 

按原样声明,我无法更改它。但即使在 C++ 中,使用引用而不是在给定情况下使用指针也是一个好主意,就像在 Java 中一样。有解决办法吗?我找不到。

【问题讨论】:

  • 如果您使用的是接受 C 字符串的 API,则无法在 somewhere 处进行转换。不过,对于一个常数,如果可能的话,我建议std::string_view。它不分配,但与std::string 一样有用。
  • 除非你真的需要动态分配它(你可能不需要)从使用JavaVMOption options;开始。如果坚持使用类似 C 的代码(例如,指向 char 而不是字符串的指针),您可能希望为 it 创建一个包装器,让您在有意义的地方使用 std::string,并支持创建在需要时使用实际的JavaVMOption
  • char* 演员阵容非常可怕。当然是在options[0].optionString = 的上下文中。因为它看起来根本不是一个字符串,尽管有名字。
  • 好的,我猜 std::string_view 可能是一个很好的整数。距离 C++ 这么久远,我不知道。我会仔细看看。

标签: c++ string c++11 c++14


【解决方案1】:

"Hello" 是 6 个 char 的常量缓冲区,其值为 H e l l o \0,其生命周期是整个程序。

std::string bob = "Hello";

将该缓冲区复制到std::string 对象中。 std::string 是一个值语义对象;这是 Java 往往缺乏的东西,因为所有对象都是通过引用隐式传递的。

options[0].optionString = (char *)"-Djava.class.path=.";

这是非常危险的。您将该缓冲区转换为指向char 的非const 指针,然后将其分配给optionString

我不知道optionString 是什么,但如果它是char* 类型的变量,这会让你面临未定义的行为。对缓冲区"-Djava.class.path=." 的任何编辑都是未定义的行为,存储指向此类缓冲区的非const 指针只是在乞求这种情况发生。

简而言之,optionString 的类型是其危险程度的关键。这只是鲁莽,还是真的很愚蠢?

JavaVMOption* options = new JavaVMOption[1];

这会在堆上创建一个大小为1JavaVMOption 数组,然后存储指向options 中第一个元素的指针。

这里有很多无意义的东西。并且销毁它需要将它作为一个数组销毁?

JavaVMOption options;

这会在名为options 的堆栈上创建一个JavaVMOption。它在作用域结束时自动销毁。这似乎更实用。除非你需要new,否则使用new 是没有意义的,而在C++ 中你很少需要new

我找到了一些示例代码:

#include <jni.h>       /* where everything is defined */
...
JavaVM *jvm;       /* denotes a Java VM */
JNIEnv *env;       /* pointer to native method interface */
JavaVMInitArgs vm_args; /* JDK/JRE 6 VM initialization arguments */
JavaVMOption* options = new JavaVMOption[1];
options[0].optionString = "-Djava.class.path=/usr/lib/java";
vm_args.version = JNI_VERSION_1_6;
vm_args.nOptions = 1;
vm_args.options = options;
vm_args.ignoreUnrecognized = false;
/* load and initialize a Java VM, return a JNI interface
 * pointer in env */
JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
delete options;
/* invoke the Main.test method using the JNI */
jclass cls = env->FindClass("Main");
jmethodID mid = env->GetStaticMethodID(cls, "test", "(I)V");
env->CallStaticVoidMethod(cls, mid, 100);
/* We are done. */
jvm->DestroyJavaVM();

这不是由以 C++ 编程为生的人编写的。

这是我最初的尝试:

struct destroy_java_vm {
  void operator()(JavaVM* jvm)const{
    jvm->DestroyJavaVM();
  }
};
using up_java_vm = std::unique_ptr< JavaVM, destroy_java_vm >;

struct java_vm {
  up_java_vm vm;
  JNIEnv* env = 0;
};
struct java_vm_option {
  std::string string;
  std::shared_ptr<void> extra_info;
};
using java_vm_options = std::vector<java_vm_option>;

struct java_vm_init {
  unsigned version = JNI_VERSION_1_2;
  java_vm_options options;
  bool ignore_unrecognized = false;
  java_vm init() {
    std::vector<JavaVMOption> java_options(options.size());
    for (std::size_t i = 0; i < options.size(); ++i) {
      java_options[i].optionString = &options.string[0];
      java_options[i].extraInfo = options.extra_info.get();
    }
    JavaVMInitArgs args;
    args.version = version;
    args.options = java_options.data();
    args.nOptions = java_options.size();
    args.ignoreUnrecognized = ignore_unrecognized?TRUE:FALSE;
    java_vm retval;
    JavaVM* tmp = 0;
    auto res = JNI_CreateJavaVM(&tmp, (void **)&retval.env, &args);
    if (res < 0) {
      return {}; // error message?  How?
    }
    retval.vm.reset( tmp );
    return retval;
  }
}

用途:

java_vm_init init;
init.options.push_back("-Djava.class.path=.");
auto vm = init.init();
if (!vm.env) { return /* error case */; }
/* invoke the Main.test method using the JNI */
jclass cls = vm.env->FindClass("Main");
jmethodID mid = vm.env->GetStaticMethodID(cls, "test", "(I)V");
vm.env->CallStaticVoidMethod(cls, mid, 100);
/* We are done. */

根据我对unique_ptr 的使用,java_vm 对象是一种只能移动的值类型。当它被销毁时,java vm 会自动被销毁(如果你没有将它移出)。

当直接与 API 对话时,我们必须转向 C 风格的代码,因为许多跨语言 API 是使用 C 风格的接口编写的。我们将 C 风格的接口包装成更安全、更易于使用的 C++ 类型。

代码未经测试,可能包含拼写错误。

【讨论】:

  • @TobySpeight 已修复。还缺少)
  • 您找到的示例代码就是重点。目标是从 Java 访问操作系统的服务。所需的接口必须用 c 或 c++ 编写,编译并链接为库,Java VM 可以加载该库以通过 JNI 访问 OS 服务。您说示例代码不是由为生活编写 c++ 的人编写的。 Proabely JNI 接口也不是,只是普通的旧 C。我只是试图在丛林中找到一种方法,我试图了解我在做什么!因此问题的原因。你实现了我的目标,用更安全的 C++ 类型包装 C 风格的接口。
  • @Juerg 我找到的示例代码使用new,但编写起来像一个C程序。我试图勾勒出一种在 C++ 中干净利落的方法,将类 C 接口的细节隐藏在一些精简的 C++ 类型后面。
  • 谢谢雅克。我会尽快尝试的。我实际上正在改造房子,因此同时完成了数百个任务! :))
【解决方案2】:

我是一名 java 程序员,我在尝试使用 JNI 从 C++ 调用 Java 时发现了相同的示例。也就是说,我不知道这是否“正确”,但它通过更改原始代码使代码编译没有错误:

JavaVMOption* options = new JavaVMOption[1];   // JVM invocation options
options[0].optionString = "-Djava.class.path=."

改为使用 char 数组。

JavaVMOption* options = new JavaVMOption[1];
char classpath[] = "-Djava.class.path=.";
options[0].optionString = classpath;

我的基本理解是 char* 和 char[] 是一样的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-03-26
    • 2010-12-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-10-12
    • 1970-01-01
    相关资源
    最近更新 更多