在Objective-C中模拟方法重载

3

我是一个爱好者,正在用Objective-C编写数学库,现在我需要一种方式来模拟C++风格的函数重载。

我的预期效果:

NSNumber *x = @(25);
CMVector *v = [CMVector vectorWithElements:@12, @14, @18, nil];
CMMatrix *m = [CMMatrix matrixWithRows:@[@12, @13, @0 ],
                                       @[@24, @26, @30],
                                       @[@0,  @0,  @1 ], nil];
CMMatrix *i = [CMMatrix identityMatrixWithRank:3];

NSLog(@"%@ %@ %@ %@", [x multiply:v], [x multiply:m], [m multiply: i], [v multiply:x]);

有没有实现这个的方法?

我想到了一种可能的方法,使用一个类来表示一个重载函数,并将参数名编码到类方法名中,例如:

@interface CMMultiply : NSProxy

+ (NSNumber *)NSNumber:(NSNumber *)left NSNumber:(NSNumber *)right;
+ (CMVector *)NSNumber:(NSNumber *)left CMVector:(CMVector *)right;
+ (CMVector *)CMVector:(CMVector *)left NSNumber:(NSNumber *)right;
+ (CMVector *)CMVector:(CMVector *)left CMVector:(CMVector *)right;
+ (CMMatrix *)NSNumber:(NSNumber *)left CMMatrix:(CMMatrix *)right;
+ (CMMatrix *)CMMatrix:(CMMatrix *)left NSNumber:(NSNumber *)right;
+ (CMMatrix *)CMMatrix:(CMMatrix *)left CMMatrix:(CMMatrix *)right;

@end

通过创建编码后的方法名称并调用它来实现重载方法。您有更好的想法吗?
编辑:以下是我的重载逻辑:
@implementation NSObject (CMOverloading)

- (NSString *)overloadingClassName
{
    return NSStringFromClass([self class]); // Override this to handle class clusters
}

- (id)callOverloadedMethod:(Class)method withObject:(id)other
{
    NSString *methodName = [NSString stringWithFormat:@"%@:%@:", [self overloadingClassName], [other overloadingClassName]];
    SEL selector = NSSelectorFromString(methodName);
    return objc_msgSend(method, selector, self, other);
}

@end

编辑2

“重载库”的基类是NSProxy,因此该库的用户无法实例化它。

编辑3

实现简化宏:

#define CMOverloadingMethod(_return, _left, _right) \
+ (_return *)_left:(_left *)left _right:(_right *)right

#define CMOverloadedMethod(_type, _object) \
return [self callOverloadedMethod:[_type class] withObject:(_object)]

重载方法仍需要一个(简单的)单一实现,在这里,可以直接在NSObject中实现:

- (id)multiply:(id)right
{
    CMOverloadedMethod(CMMultiply, right);
}

并通过 NSNumber 实现它:

@implementation CMMultiply (NSNumber)

CMOverloadingMethod(NSNumber, NSNumber, NSNumber)
{
    return @([left doubleValue] * [right doubleValue]);
}

@end

1
为每个操作创建一个类是疯狂的!如果你真的需要运算符重载——你应该仔细考虑一下——一种方法是将方法的参数指定为类型“id”。然后,您可以使用isKindOfClass:来确定您需要对它们执行的操作。 - bdesham
@bdesham 嗯,这个“每个重载一个类”的设计意图是与类别(class categories)一起使用的,以便稍后可以添加不同的重载。 - Maxthon Chan
4个回答

2
为了实现这种语法
NSLog(@"%@ %@ %@ %@", [x multiply:v], [x multiply:m], [m multiply: i], [v multiply:x]);

您需要在每个数学类(向量、矩阵)中实现一个乘法方法,并在NSNumber上实现一个类别方法。问题是,您还希望能够传递任何参数,而Objective-C不支持这一点(具有相同名称但不同参数类型的多个方法)。

相反,您可以将id作为参数,并在运行时确定类型,或者您需要更加详细:

NSLog(@"%@ %@ %@ %@", [x multiplyWithVector:v], 
                      [x multiplyWithMatrix:m],
                      [m multiplyWithMatrix:i],
                      [v multiplyWithScalar:x]);

但是这样做过于复杂,而且实现这样的数学库会相当慢。

为什么不使用简单的结构体和函数呢?这已经被多次尝试并且行之有效。


我说的是开玩笑,如果是真的,我宁愿直接使用GMP/MPFR或类似的工具。 - Maxthon Chan

1

Objective-C不像C++一样使用虚函数表(vtable)。它使用字符串查找来引用方法,因此您无法通过参数重载方法。因此,最好的方法是使用id来允许引用任何类型的参数。因此,您的multiply方法将如下所示:

+ (id)multiplyLeft:(id)left right:(id)right;

不是最美观的解决方案。

你可以使用分类,这样你就有了一个以以下方式开始的解决方案:

@interface NSObject (Math)

- (instancetype)multiply:(id)right;

@end

@implementation NSObject (Math)

- (instancetype)multiply:(id)right
{
    // Feel free to throw an exception here.
    return nil;
}

@end

@implementation NSNumber (Math)

- (instancetype)multiply:(id)right
{
    if ([right isKindOfClass:[NSNumber class]])
    {
        return [NSNumber numberWithInteger:[self integerValue] * [right integerValue]];
    }
    else
    {
    // Feel free to throw an exception here.
        return nil;
    }
}

@end

虽然不是很完美的解决方案,但它可以让你写出自己想要的内容。

祝你好运。


我实际上正在尝试在字符串匹配的基础上实现一种基于类型的虚函数表查找。 - Maxthon Chan

0
这是我使用的最终设计,利用反射解决类型问题。我仍然使用类作为重载函数的容器,实质上将这些容器类型作为虚表格使用。
#define CMOverloadingMethod(_return, _left, _right) \
+ (_return *)_left:(_left *)left _right:(_right *)right

#define CMOverloadedMethod(_type, _object) \
return [self callOverloadedMethod:[_type class] withObject:(_object)]

- (id)callOverloadedMethod:(Class)method withObject:(id)other
{
    Class thisClass = [self class];
    Class nextClass = [other class];
    Class methodClass = object_getClass(method);

    // Find the appropriate overload:
    Class thisClass2 = Nil;
    do
    {
        thisClass2 = thisClass;

        Class otherClass = nextClass;
        Class otherClass2 = Nil;
        do
        {
            otherClass2 = otherClass;
            SEL selector = NSSelectorFromString(MSSTR(@"%@:%@:", NSStringFromClass(thisClass), NSStringFromClass(otherClass)));
            if (class_respondsToSelector(methodClass, selector))
                return objc_msgSend(method, selector, self, other);
            else
                otherClass = class_getSuperclass(otherClass);
        } while (otherClass != otherClass2 && otherClass && otherClass2);
        thisClass = class_getSuperclass(thisClass);
    } while (thisClass != thisClass2 && thisClass && thisClass2);

    [NSException raise:NSInvalidArgumentException format:@"Cannot resolve overloaded method in class %@ for objects typed %@ and %@.", NSStringFromClass(method), NSStringFromClass([self class]), NSStringFromClass([other class])];
    return nil;
}

0

你也可以使用C++。Objective-C++非常强大。现在的Clang支持C++ 11。这是C++非常擅长的领域,两种语言的代码混合起来非常干净和容易。


我对于C++和林纳斯·托瓦兹(Linus Torvalds)的想法差不多,就是彻底地讨厌它。因此,我会继续使用Objective-C或者只用纯C。 - Maxthon Chan
哦,天哪,如果Linus说不好的话...哈哈。好吧,没问题。完成后一定要发布你的解决方案。也许到那时我会发布一个C++版本。 - Rob
好的,你在帖子中说你正在尝试模拟C ++,然后当我指出你可以轻松地交织C ++和目标语言时,你说你讨厌它。这很有道理。我保证组合最终看起来会比这个干净得多。更不用说它可能会比它表现出一个数量级。但是对不起我说了什么,伙计。 - Rob
另外,如果我要使用C++,那么我就必须将我的整个库重写为C++,所以,算了吧。C++的重载可能能够区分intid(Objective-C对象),但它无法区分NSNumber *CMVector *,因为它们在C++中都是id。因此,需要一个本地的Objective-C方法。既然C++没有帮助,那还有什么意义呢? - Maxthon Chan
那比“Linus不喜欢它”更好的论据。我不知道你在类型方面走了多远。你有没有看过Boost?对于像这样的东西,它超越了我曾经见过的所有其他东西...别误会,我喜欢O-C,但对于像矩阵数学这样的东西,我会使用C ++。 - Rob

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接