gpt4 book ai didi

kotlin - 在从构造函数或 init block 调用的重写抽象函数中初始化变量时未正确初始化变量

转载 作者:行者123 更新时间:2023-12-02 13:38:51 25 4
gpt4 key购买 nike

我遇到了一些 Kotlin 代码的问题,我发现它与调用从 init block 中分配一些变量的方法有关(或与此相关的辅助构造函数,要么重现问题)。

MCVE:

abstract class Shader(/*Input arguments omitted for the sake of an MCVE*/){

init{
//Shader loading and attaching, not relevant
bindAttribs()//One of the abstract methods. In my actual program, this uses OpenGL to bind attributes
//GLSL program validation
getUniforms()//Same as the previous one: abstract method using GL calls to get uniforms. This gets locations so an integer is set (the problem)

}
abstract fun getUniforms();//This is the one causing problems
abstract fun bindAttribs();//This would to if primitives or non-lateinit vars are set
}

abstract class BoilerplateShader() : Shader(){
var loc_projectionMatrix: Int = 404//404 is an initial value. This can be anything though
var loc_transformationMatrix: Int = 404
var loc_viewMatrix: Int = 404

override fun getUniforms(){
//These would be grabbed by using glGetUniformLocations, but it's reproducable with static values as well
loc_projectionMatrix = 0
loc_transformationMatrix = 1
loc_viewMatrix = 2
println(loc_projectionMatrix.toString() + ", " + loc_transformationMatrix + ", " + loc_viewMatrix)
}

//debug method, only used to show the values
fun dump(){
println(loc_projectionMatrix.toString() + ", " + loc_transformationMatrix + ", " + loc_viewMatrix)
}

}

class TextureShader() : BoilerplateShader(){

override fun bindAttribs() {
//This doesn't cause a problem even though it's called from the init block, as nothing is assigned
//bindAttrib(0, "a_position");
//bindAttrib(1, "a_texCoord0");
}
}

//Other repetitive shaders, omitted for brevity

然后做:
val tx = TextureShader()
tx.dump()

打印:
0, 1, 2
404, 404, 404

打印语句按从 getUniforms 到最后的转储调用的顺序调用。它在 getUniforms 中分配得很好方法,但是在几毫秒后调用它们时,它们突然被设置为(在这种情况下)404的默认值。这个值可以是任何东西,但我使用404,因为我知道我不会用于在这个特定的 MCVE 中进行测试。

我正在使用一个严重依赖抽象类的系统,但调用其中一些方法( getUniforms 非常重要)是必须的。如果我在 BoilerplateShader 中添加一个初始化 block 或 TextureShader调用 getUniforms ,它工作正常。使用在对象创建后调用的 init 函数(不是 init block )进行变通:
fun init(){
bindAttribs();
getUniforms();
}

工作正常。但这将涉及创建的实例手动调用它:
val ts = TexturedShader();
ts.init();
ts.dump()

这不是一个选择。在 Java 中编写导致 Kotlin 出现问题的代码的工作方式与预期的一样(大大缩短了代码,但仍然可以重现):
abstract class Shader{
public Shader(){
getUniforms();
}

public abstract void getUniforms();
}

abstract class BoilerplateShader extends Shader{
int loc_projectionMatrix;//When this is initialized, it produces the same issue as Kotlin. But Java doesn't require the vars to be initialized when they're declared globally, so it doesn't cause a problem
public void getUniforms(){
loc_projectionMatrix = 1;
System.out.println(loc_projectionMatrix);
}
//and a dump method or any kind of basic print statement to print it after object creation
}

class TextureShader extends BoilerplateShader {
public TextureShader(){
super();
}
}

并在初始化变量和类后打印变量的值,如预期的那样打印 0。

尝试用一个对象重现相同的东西会产生与使用数字 相同的结果当 var 不是 lateinit .所以这:
var test: String = ""

打印:
0, 1, 2, test
404, 404, 404,

最后一行与打印的完全一样:值 if test默认设置为空字符串,因此显示为空。

但是如果 var 被声明为 lateinit var :
lateinit var test: String

它打印:
0, 1, 2, test
404, 404, 404, test

can't declare primitives with lateinit .由于它是在构造函数之外调用的,因此需要对其进行初始化或声明为 lateinit .

那么,是否可以在不创建函数来调用它的情况下从重写的抽象方法初始化原语?

编辑:

评论建议使用工厂方法,但由于抽象,这不会起作用。由于尝试的目标是从基类( Shader )调用方法,并且由于无法初始化抽象类,因此如果不在每个类中创建手动实现,工厂方法将无法工作,这是矫枉过正的。如果构造函数是私有(private)的以使其工作(避免在工厂方法之外初始化),扩展将不起作用( <init> is private in Shader )。

因此构造函数被强制公开(无论 Shader 类有主构造函数还是辅助构造函数,子类都必须有一个主构造函数才能对其进行初始化),这意味着可以在绕过工厂方法的同时创建着色器。而且,抽象又会导致问题,工厂方法(必须是抽象的)将在每个子类中手动实现,再次导致初始化并手动调用 init()方法。

问题仍然是在从构造函数调用抽象方法时是否可以确保非延迟初始化和原语被初始化。如果不涉及抽象,创建工厂方法将是一个完美的解决方案。

最佳答案

注意:绝对最好的想法是避免在从抽象类的构造方法调用的抽象函数中声明对象/基元,但在某些情况下它很有用。尽可能避免它。

我找到的唯一解决方法是使用 by lazy ,因为涉及到原语,我可以将分配转换为在 block 中工作。
lateinit会稍微容易一些,所以创建对象包装器当然是一种选择,但使用 by lazy在我的情况下有效。

无论如何,这里发生的事情是在构造函数中分配给 int 的值稍后会被固定值覆盖。伪代码:

var x /* = 0 */
constructor() : super.constructor()//x is not initialized yet
super.constructor(){
overridden function();
}
abstract function()
overridden function() {
x = 4;
}
// The assignment if `= 0` takes place after the construction of the parent, setting x to 0 and overriding the value in the constructor

使用lateinit,问题就解决了:
lateinit var x: Integer//x exists, but doesn't get a value. It's assigned later
constructor() : super.constructor()
super.constructor(){
overridden function()
}
abstract function()
overridden function(){
x = Integer(4);//using an object here since Kotlin doesn't support lateinit with primtives
}
//x, being lateinit and now initialized, doesn't get re-initialized by the declaration. x = 4 instead of 0, as in the first example

当我写这个问题时,我认为 Java 的工作方式不同。这是因为我也没有在那里初始化变量(实际上是让它们延迟初始化)。当类被完全初始化时, int x;没有被赋值。如果它被声明为 int x = 1234; ,Java 中出现与此处相同的问题。

现在,问题又回到了 lateinit 和原语;原语不能是lateinit。一个相当基本的解决方案是使用数据类:
data class IntWrapper(var value: Int)

由于数据类的值可以解包:
var (value) = intWrapperInstance//doing "var value = ..." sets value to the intWrapperInstance. With the parenthesis it works the same way as unpacking the values of a pair or triple, just with a single value.

现在,由于有一个带有对象(不是原始对象)的实例,所以可以使用 lateinit 。但是,这并不是特别有效,因为它涉及创建另一个对象。

唯一剩下的选项: by lazy .

只要可以将初始化创建为函数,这是最好的选择。问题中的代码是 OpenGL 着色器的简化版本(更具体地说,是制服的位置)。这意味着此特定代码很容易转换为 by lazy堵塞:
val projectionMatrixLocation by lazy{
glGetUniformLocation(program, "projectionMatrix")
}

但根据具体情况,这可能不可行。尤其是因为 by lazy需要 val ,这意味着以后无法更改它。不过,这取决于使用情况,因为如果它不会改变,这不是问题。

关于kotlin - 在从构造函数或 init block 调用的重写抽象函数中初始化变量时未正确初始化变量,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48045901/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com